Eu tenho uma tabela containers
que pode ter um relacionamento muitos-para-muitos com várias tabelas, digamos que sejam plants
, animals
e bacteria
. Cada recipiente pode conter um número arbitrário de plantas, animais ou bactérias, e cada planta, animal ou bactéria pode estar em um número arbitrário de recipientes.
Até agora, isso é muito simples, mas a parte com a qual estou tendo problemas é que cada contêiner deve conter apenas elementos do mesmo tipo. Recipientes misturados que contêm plantas e animais, por exemplo, devem constituir uma violação de restrição no banco de dados.
Meu esquema original para isso foi o seguinte:
containers
----------
id
...
...
containers_plants
-----------------
container_id
plant_id
containers_animals
------------------
container_id
animal_id
containers_bacteria
-------------------
container_id
bacterium_id
Mas, com esse esquema, não consigo descobrir como implementar a restrição de que os contêineres devem ser homogêneos.
Existe uma maneira de implementar isso com integridade referencial e garantir no nível do banco de dados que os contêineres são homogêneos?
Estou usando o Postgres 9.6 para isso.
fonte
Respostas:
Existe uma maneira de implementar isso declarativamente apenas sem alterar muito sua configuração atual, se você concordar em introduzir alguma redundância nela. O que se segue pode ser considerado um desenvolvimento da sugestão de RDFozz , embora a ideia tenha se formado completamente em minha mente antes de ler sua resposta (e é diferente o suficiente para garantir sua própria resposta).
Implementação
Aqui está o que você faz, passo a passo:
Crie uma
containerTypes
tabela ao longo da linha sugerida na resposta do RDFozz:Preencha-o com IDs predefinidos para cada tipo. Para os fins desta resposta, eles devem corresponder ao exemplo de RDFozz: 1 para plantas, 2 para animais, 3 para bactérias.
Adicione uma
containerType_id
colunacontainers
e torne-a não anulável e uma chave estrangeira.Supondo que a
id
coluna já seja a chave primária decontainers
, crie uma restrição exclusiva(id, containerType_id)
.É aqui que os despedimentos começam. Se
id
for declarada a chave primária, podemos ter certeza de que é única. Se for único, qualquer combinação deid
e outra coluna também será única, sem declaração adicional de exclusividade - então, qual é o sentido? O ponto é que, ao declarar formalmente o par de colunas exclusivo, permitimos que eles sejam referenciáveis , ou seja, para ser o alvo de uma restrição de chave estrangeira, que é a parte desta parte.Adicionar uma
containerType_id
coluna a cada uma das tabelas de junção (containers_animals
,containers_plants
,containers_bacteria
). Tornar uma chave estrangeira é completamente opcional. O que é crucial é garantir que a coluna tenha o mesmo valor para todas as linhas, diferente para cada tabela: 1 paracontainers_plants
, 2 paracontainers_animals
, 3 paracontainers_bacteria
, de acordo com as descrições emcontainerTypes
. Em cada caso, você também pode tornar esse valor o padrão para simplificar suas instruções de inserção:Em cada uma das tabelas de junção, torne o par de colunas
(container_id, containerType_id)
uma referência de restrição de chave estrangeiracontainers
.Se
container_id
já estiver definido como uma referênciacontainers
, sinta-se à vontade para remover essa restrição de cada tabela, pois não é mais necessária.Como funciona
Adicionando a coluna de tipo de contêiner e fazendo com que ela participe das restrições de chave estrangeira, você prepara um mecanismo que impede a alteração do tipo de contêiner. Alterar o tipo no
containers
tipo seria possível apenas se as chaves estrangeiras fossem definidas com aDEFERRABLE
cláusula, que elas não deveriam estar nesta implementação.Mesmo se eles fossem adiados, a alteração do tipo ainda seria impossível devido à restrição de verificação do outro lado do
containers
relacionamento da tabela de funções. Cada tabela de junção permite apenas um tipo de contêiner específico. Isso não apenas impede que as referências existentes alterem o tipo, mas também impede a adição de referências de tipo incorretas. Ou seja, se você tiver um contêiner do tipo 2 (animais), poderá adicionar itens a ele apenas usando a tabela em que o tipo 2 é permitido, ou sejacontainers_animals
, e não seria possível adicionar linhas referenciando-o a, digamoscontainers_bacteria
, que aceite apenas recipientes do tipo 3.Finalmente, sua própria decisão de ter tabelas diferentes para
plants
,animals
ebacteria
, e tabelas de junção diferentes para cada tipo de entidade, já torna impossível para um contêiner ter itens de mais de um tipo.Portanto, todos esses fatores combinados garantem, de maneira puramente declarativa, que todos os seus contêineres serão homogêneos.
fonte
Uma opção é adicionar
containertype_id
a àContainer
tabela. Torne a coluna NOT NULL e uma chave estrangeira para umaContainerType
tabela, que teria entradas para cada tipo de item que pode ir em um contêiner:Para garantir que o tipo de contêiner não possa ser alterado, crie um gatilho de atualização que verifique se o
containertype_id
foi atualizado e reverta a alteração nesse caso.Em seguida, ao inserir e atualizar gatilhos nas tabelas de links de contêiner, verifique o containertype_id com o tipo de entidade nessa tabela, para garantir que eles correspondam.
Se o que você colocar em um contêiner precisar corresponder ao tipo, e o tipo não puder ser alterado, tudo no contêiner será do mesmo tipo.
NOTA: Como o gatilho nas tabelas de links é o que decidirá quais correspondências, se você precisar de um tipo de contêiner que possa conter plantas e animais, é possível criar esse tipo, atribuí-lo ao contêiner e verificar se esse . Portanto, você mantém a flexibilidade se as coisas mudarem em algum momento (digamos, você obtém os tipos "revistas" e "livros" ...).
OBSERVE a segunda: se a maior parte do que acontece com os contêineres é a mesma, independentemente do que está neles, isso faz sentido. Se você tem coisas muito diferentes que acontecem (no sistema, e não em nossa realidade física) com base no conteúdo do contêiner, a ideia de Evan Carroll de ter tabelas separadas para os tipos de contêineres faz muito sentido. Esta solução estabelece que os contêineres têm tipos diferentes na criação, mas os mantém na mesma tabela. Se você precisar verificar o tipo toda vez que executar uma ação em um contêiner, e se a ação que você executar depender do tipo, tabelas separadas poderão realmente ser mais rápidas e fáceis.
fonte
Se você precisa apenas de 2 ou 3 categorias (plantas / metazoa / bactérias) e deseja modelar um relacionamento XOR, talvez um "arco" seja a solução para você. Vantagem: não há necessidade de gatilhos. Diagramas de exemplo podem ser encontrados [aqui] [1]. Na sua situação, a tabela "containers" teria 3 colunas com uma restrição CHECK, permitindo uma planta ou animal ou bactéria.
Provavelmente isso não é apropriado se houver a necessidade de distinguir entre muitas categorias (por exemplo, gêneros, espécies, subespécies) no futuro. No entanto, para 2-3 grupos / categorias, isso pode funcionar.
ATUALIZAÇÃO: Inspirada nas sugestões e comentários do colaborador, uma solução diferente que permite muitos táxons (grupos de organismos relacionados, classificados pelo biólogo) e evita nomes de tabelas "específicos" (PostgreSQL 9.5).
Código DDL:
Dados de teste:
Teste:
Agradeço a @RDFozz e @Evan Carroll e @ypercube por sua contribuição e paciência (lendo / corrigindo minhas respostas).
fonte
Primeiro, concordo com o @RDFozz na leitura da pergunta. No entanto, ele levanta algumas preocupações sobre a resposta dos stefans ,
Para resolver suas preocupações, apenas
PRIMARY KEY
UNIQUE
restrições para proteger contra entradas duplicadas.EXCLUSION
restrições para garantir que os contêineres sejam "homogêneos"c_id
para garantir um desempenho decente.Aqui está o que parece,
Agora você pode ter um contêiner com várias coisas, mas apenas um tipo de coisa em um contêiner.
E tudo é implementado nos índices GIST.
A Grande Pirâmide de Gizé não tem nada no PostgreSQL.
fonte
Essa é uma péssima ideia.
E agora você sabe o porquê. =)
Acredito que você esteja preso à idéia de herança da programação orientada a objetos (OO). A herança de OO resolve um problema com a reutilização de código. No SQL, código redundante é o menor dos nossos problemas. A integridade é antes de tudo. O desempenho costuma ser o segundo. Sentiremos prazer nos dois primeiros. Não temos um "tempo de compilação" que pode eliminar os custos.
Portanto, apenas abandone sua obsessão pela reutilização de código. Recipientes para plantas, animais e bactérias são fundamentalmente diferentes em todos os lugares do mundo real. O componente de reutilização de código de "retém coisas" simplesmente não fará isso por você. Divida-os. Não apenas isso lhe proporcionará mais integridade e mais desempenho, mas, no futuro, você achará mais fácil expandir seu esquema: afinal, no seu esquema, você já teve que separar os itens contidos (plantas, animais, etc.) , parece pelo menos possível que você precise separar os contêineres. Você não vai querer redesenhar todo o seu esquema então.
fonte
plant_containers
e assim por diante. Coisas que precisam apenas de um contêiner de planta são selecionadas apenas daplant_containers
tabela. Coisas que precisam de qualquer contêiner (ou seja, pesquisando todos os tipos de contêineres) podem fazerUNION ALL
nas três tabelas com contêineres.