Eu me deparei com um problema durante o design do banco de dados. Deixe-me explicar do que se trata:
O contrato tem alguns atributos simples que o descrevem, podendo ter anexos e dinâmica de pagamento.
Portanto, decidi fazer a tabela principal Contracts
e coloquei atributos simples como colunas.
A dinâmica de pagamento é um atributo complexo da Contracts
tabela. Pode ter nenhum, um ou vários valores. Pesquisando na Internet aprendi que isso se chama atributo multivalorado , e encontrei este exemplo que parece ilustrar muito bem o meu caso (Dinâmica de pagamento é equivalente à tabela Hobbies no exemplo vinculado).
Quanto aos Anexos, é um atributo complexo de Contract
. O contrato pode ter muitos deles, um ou nenhum.
Anexo tem exatamente os mesmos atributos simples do contrato, podendo também ter Dinâmica de pagamento. A relação do Anexo com a Dinâmica de pagamento é a mesma que a relação entre Contrato e Dinâmica de pagamento.
Resumindo, Anexo e contrato têm tudo igual, a única diferença é que Anexo é um atributo complexo de Contrato.
Usando isso como referência, fiz o esboço do meu diagrama ER:
Esta é a primeira vez que vejo este tipo de relacionamento, então peço à comunidade para me ajudar a converter este diagrama ER em SQL ou tabelas relacionais.
Peço desculpas por não fornecer mais informações, se você tiver mais solicitações, deixe um comentário e atualizarei minha postagem imediatamente.
Obrigado por sua compreensão e ajuda.
Atenciosamente.
Concordo que a abordagem de @MladenUzelac é provavelmente o padrão mais comum para essa situação, mas há uma falha teórica em potencial que pode se expor ao longo do tempo e dificultar a vida. Essa falha é: um Contrato , no final das contas, não é um Anexo . Claro, eu enfatizei "potencial" porque atualmente não estou a par se eles são ou não considerados a mesma coisa (ou seja, Contrato === Anexo). No entanto, até certo ponto, isso não mudaria minha recomendação de descobrir que eles são os mesmos. Qualquer coisa está sujeita a mudanças a qualquer momento, mesmo que altamente improvável, então ter a mesma natureza hoje não implica que eles sempre terão a mesma natureza, e muitas vezes as coisas crescem em direções diferentes. Então, sim, eles compartilham os mesmos atributos hoje e compartilham o mesmo relacionamento com a Dinâmica de Pagamento hoje; mas amanhã é outro dia e a natureza das coisas muda, mesmo por razões irracionais ;-).
Não me interpretem mal: definitivamente há momentos em que uma entidade autorreferenciada é a solução apropriada, mas acho que isso requer que as entidades sejam da mesma natureza. Os dois exemplos mais comuns que consigo pensar são:
Funcionário / Gerente: Faz sentido ter um campo [ManagerID] (ou eu prefiro [ManagerUserID]) na tabela [Employee] que é um FK auto-referenciado de volta para [UserID]. Isso funciona porque um gerente é um empregado.
Departamentos: Em hierarquias corporativas/varejo, faz sentido ter um campo [ParentDepartmentID] na tabela [Department] que é um FK autorreferenciado de volta para [DepartmentID]. Isso funciona porque o Departamento "pai" é um Departamento.
Em ambos os casos, o relacionamento entre pai e filho não é uma propriedade determinante primária dessas entidades. Em vez disso, o relacionamento é uma propriedade, assim como o nome da entidade. Em ambos os casos, o relacionamento não é crítico para a existência das entidades filhas, e as entidades definidas como "filhos" existem mesmo sem o relacionamento. Por outro lado, se um Anexo requer um Contrato para existir, bem, isso é algo bem diferente.
O que importa é: só porque duas coisas parecem iguais não significa que sejam iguais.
Armazenar duas entidades "semelhantes, mas diferentes" juntas, enquanto reduz uma certa quantidade de tabelas / junções / código, leva a algumas preocupações pragmáticas, mesmo que apenas em termos de armazenamento físico (ou seja, a camada de banco de dados).
Trabalhei durante anos em um sistema, construído antes de chegar lá, no qual esse padrão foi usado várias vezes e para situações quase idênticas ao que está sendo descrito aqui. Não tenho certeza se as entidades tinham exatamente os mesmos atributos no dia 1, mas ao longo do tempo, pelo menos, elas começaram a divergir e assumir propriedades diferentes umas das outras. Em alguns casos foram adicionados campos que eram apropriados para uma das entidades, mas não para outra, então só tínhamos que saber quando um campo NULL era apropriado para aquele tipo de linha, ou se era um bug. Outras vezes, uma abordagem de "atributo multivalorado" foi adotada com a estrutura sendo efetivamente: [EntityID], [AttributeID], [AttributeValue].
Como esperado, para obter uma lista de entidades "pai", consultaríamos com
WHERE ParentEntityID IS NULL
. E para obter uma lista de entidades "filhos" com as quais consultaríamosWHERE ParentEntityID IS NOT NULL
. Ficou divertido quando precisávamos de ambas as entidades porque elas eram realmente uma propriedade de outra entidade primária, então precisávamos mostrar ambas como propriedades separadas da entidade primária. O que foi divertido foi como essa junção de auto-referência prejudicou o desempenho (embora seja possível que uma ligeira mudança na estratégia de indexação possa ter consertado isso). Mas ficou superdivertido quando colocamos esse EntityID em outras entidades como FKs, e só queríamos fazer referência a um desses dois tipos de entidade, mas nem sempre era garantido que queríamos a entidade "filho" (embora esse fosse o entidade mais comum para se referir). No final, quando comecei a criar novas tabelas e refatorar as antigas, incluí o nome da entidade no nome do campo FK na entidade relacionada, apenas para "autodocumentar" para quem procura nas tabelas e precisa criar novas consultas. E, em alguns casos, adicionei dois campos FK, um para cada tipo de entidade, apenas para evitar essa junção automática cara (e era seguro desnormalizar, pois o relacionamento pai-filho nunca poderia mudar depois de criado).Então, eu começaria indo na mesma direção que @Peter em termos de tabelas separadas para Contrato e Anexo (mesmo que sejam estruturas idênticas ou quase idênticas), mas abordaria o tratamento de seu relacionamento com a Dinâmica de Pagamento diferentemente. Parece que me lembro de ter tentado os dois campos FK diferentes no passado e não ter gostado muito de como isso aconteceu. No mínimo, parece menos gerenciável ao longo do tempo, à medida que outras entidades são adicionadas que podem se relacionar com a Dinâmica de Pagamento e você precisa continuar adicionando FKs de volta aos novos pais. E você também precisa ter um CHECK CONSTRAINT na tabela garantindo que um, e apenas um, desses campos FK seja NÃO NULO.
Em vez disso, eu criaria uma ponte/tabela de relacionamento separada para cada um dos Anexos e Contratos para se relacionar com a Dinâmica de Pagamento . Sim, quando forem adicionadas novas entidades que possam se relacionar com a Dinâmica de Pagamento você precisará adicionar novas tabelas de ponte/relacionamento, mas de alguma forma me sinto melhor com isso porque não está alterando a estrutura da Dinâmica de Pagamento : a entidade Dinâmica de Pagamento é o mesmo se 1 ou 100 entidades se relacionam com ele.
(o SQL abaixo usa a semântica do Microsoft SQL Server T-SQL)
Principais Entidades
Dinâmica de Pagamento
A tabela [DynamicsOfPaymentType] é uma tabela de pesquisa que assume que as "dinâmicas" são propriedades nomeadas, possivelmente imitando uma enumeração na camada do aplicativo. Eu escolhi TINYINT (0 - 255) porque normalmente a maioria das coisas tem menos de 255 propriedades/atributos distintos. Se precisar de mais de 255, vá para o próximo tamanho maior. Mas não vá além do necessário, pois esse campo é copiado nas tabelas de relacionamento, que podem ficar grandes, especialmente com muitos contratos e anexos , cada um com várias entradas DynamicOfPayment .
Observe que o PK em DynamicsOfPayment é uma chave composta de [DynamicsOfPaymentID] e [DynamicsOfPaymentTypeID]. Embora tecnicamente apenas o campo [DynamicsOfPaymentID] seja necessário no PK, pois é garantido como único, ter o campo [DynamicsOfPaymentTypeID] nos permite enviar essa propriedade para a tabela de relacionamento para uso na restrição de cada entidade para ter apenas uma instância de qualquer Propriedade DynamicsOfPayment . Por favor, veja as notas na próxima seção para mais detalhes.
Entidade principal para relacionamentos de Dinâmica de Pagamento
Observe que o PK de ambas as tabelas de relacionamento é uma chave composta composta pelo ID da entidade principal e o campo [DynamicsOfPaymentTypeID] -- não o campo [DynamicsOfPaymentID]. O motivo, conforme mencionado nas notas das seções anteriores, é impor uma única instância de qualquer propriedade DynamicsOfPayment específica para cada entidade principal. Se uma entidade pode ter várias instâncias de um DynamicsOfPaymentpropriedade, então é uma simples questão de ajustar o PK nessa tabela de relacionamento específica para ser uma chave composta composta pelo ID da entidade principal e o campo [DynamicsOfPaymentID] (ou, se preferir, ambos [DynamicsOfPaymentTypeID] e [DynamicsOfPaymentID ] Campos). Como você pode ver, esse modelo pode permitir não apenas que os relacionamentos imponham instâncias singulares de propriedades relacionadas OU que os relacionamentos aceitem várias propriedades relacionadas, mas também permite misturar os dois, pois as tabelas de relacionamento não precisam usar o mesma combinação de campos para seu PK: alguns relacionamentos podem ser restritos enquanto outros não. Portanto, talvez o Anexo esteja limitado a uma única instância de qualquer propriedade DynamicsOfPayment específica , mas o Contratoé permitido ter várias instâncias de uma propriedade específica.
dicas profissionais
Sim, haverá um pouco mais de tabelas, junções e código de aplicativo usando este modelo. Mas lembre-se de que refatorar o código do aplicativo é muitomais fácil do que refatorar tabelas, especialmente quando o aplicativo estiver ativo e os dados estiverem lá. Tome um pouco mais de tempo agora para codificar isso e economize muitas, muitas horas ao longo dos anos tentando fazer alterações em modelos mais simples, ou conforme os projetos surgirem em alguns anos, apenas decidindo colocar hacks / band - ajuda porque o negócio não vai demorar 10 a 20 (ou mais) horas para fazer alterações no modelo simplificado que economizou de 1 a 2 horas agora. Acredite em mim, programadores/arquitetos de banco de dados não são baratos (pelo menos não hoje em dia) e meus empregadores gastaram uma quantidade notória de tempo e dinheiro - na forma de meu salário (e dos salários de meus colegas de trabalho de banco de dados) - para para refatorar escolhas ruins de design que economizaram talvez até 1 dia do tempo inicial de desenvolvimento,
Você pode coordenar a limpeza das tabelas de relacionamento marcando o FK como
ON DELETE CASCADE
. A sintaxe pode diferir um pouco entre os RDBMSs, mas eu esperaria que a maioria, se não todos, suportasse exclusões em cascata. Desta forma, se você deletar uma entrada Dinâmica de Pagamento , não precisa se preocupar com sua entrada em nenhuma das tabelas de relacionamento.Dependendo de qual RDBMS você usa, você pode até coordenar a criação dos registros de relacionamento. O Microsoft SQL Server possui uma cláusula OUTPUT muito legal que retorna as alterações feitas por uma operação DML. Portanto, você pode inserir um registro em [DynamicsOfPayment] e, dentro do contexto dessa operação (e, portanto, transação), você pode fazer a inserção na tabela de relacionamento. Suponha que você tenha um procedimento de inserção que aceite um parâmetro extra para @ContractID que não seja inicialmente necessário para a tabela [DynamicOfPayment]. Pode ser usado da seguinte forma:
Agora você só precisa de dois procedimentos armazenados (já que cada um precisa referenciar uma tabela de relacionamento diferente):
DynamicsOfPayment_CreateForContract (@DynamicsOfPaymentTypeID, @Value, @ContractID)
DynamicsOfPayment_CreateForAnnex (@DynamicsOfPaymentTypeID, @Value, @AnnexID)
Parece que é melhor que o contrato tenha auto-junção para contrato e anexo (estrutura hierárquica).
A nomenclatura do contrato e do anexo pode ser diferente, e id e self_id podem ser diferentes.
Uma vez que o PostgreSQL suporta Writeable Common Table Expression (CTE), você pode atualizar, inserir, inserir e excluir valores.
Indicarei os guias que lhe explicarão mais:
Ótimo vídeo explicando a estrutura recursiva
Expressões de tabela comuns no PostgreSQL - Documento oficial
Consultas recursivas com postgres
Recursivo 3
Estrutura em árvore com PostgreSQL
Aqui está um tutorial da Oracle sobre esse problema:
Oracle e CTE
Vou demonstrar como você pode preservar o pedido facilmente de contrato e anexos.
Ao armazenar valores na tabela Contrato, siga este padrão:
Portanto, quando você tiver uma consulta recursiva, poderá obter facilmente os documentos oficiais solicitados.
E aqui está um pequeno curso gratuito sobre estrutura recursiva com SQL: https://class.stanford.edu/courses/DB/Recursion/SelfPaced/about
Eu criaria 3 tabelas:
Contrato: id do contrato (pkey) Attrib 1 Attrib 2 etc.
Anexo: ID do anexo (pkey) ID do contrato (fkey) Attrib 1 Attrib 2 etc.
Dinâmica: ID do pagamento (pkey) ID do contrato (fkey) ID do anexo (fkey) Atrib 1 Atributo 2 etc.
Pelo que entendi sua descrição, isso permitiria que as entradas na tabela dinâmica se relacionassem a um contrato, anexo ou ambos.