Como devo implementar em SQL o cenário descrito no seguinte diagrama Entidade-Relacionamento?
Como é mostrado, toda A
ocorrência do tipo entidade deve estar relacionada a pelo menos uma B
contraparte (indicada pelas linhas duplas de conexão), e vice-versa . Sei que devo criar as três tabelas a seguir:
CREATE TABLE A
(
a INT NOT NULL,
CONSTRAINT A_PK PRIMARY KEY (a)
);
CREATE TABLE B
(
b INT NOT NULL,
CONSTRAINT B_PK PRIMARY KEY (b)
);
CREATE TABLE R
(
a INT NOT NULL,
b INT NOT NULL,
CONSTRAINT R_PK PRIMARY KEY (a, b),
CONSTRAINT R_to_A_FK FOREIGN KEY (a)
REFERENCES A (a),
CONSTRAINT R_to_B_FK FOREIGN KEY (b)
REFERENCES B (b)
);
Mas, e quanto à implementação das restrições de participação total (ou seja, impor que cada instância de um A
ou B
esteja envolvida em no mínimo uma ocorrência de relacionamento com o outro)?
Não é fácil de fazer em SQL, mas não é impossível. Se você deseja que isso seja aplicado apenas por meio de DDL, o DBMS deve ter
DEFERRABLE
restrições implementadas. Isso pode ser feito (e pode ser verificado para funcionar no Postgres, que os implementou):Até aqui está o design "normal", onde todos
A
podem ser relacionados a zero, um ou muitosB
e todosB
podem ser relacionados a zero, um ou muitosA
.A restrição de "participação total" precisa de restrições na ordem inversa (de
A
eB
, respectivamente, referenciandoR
). TerFOREIGN KEY
restrições em direções opostas (de X para Y e de Y para X) é formar um círculo (um problema de "ovo e galinha") e é por isso que precisamos que pelo menos um deles sejaDEFERRABLE
. Neste caso, temos dois círculos (A -> R -> A
eB -> R -> B
, portanto, precisamos de duas restrições adiáveis:Então podemos testar se podemos inserir dados. Observe que o
INITIALLY DEFERRED
não é necessário. Poderíamos ter definido as restrições como,DEFERRABLE INITIALLY IMMEDIATE
mas então teríamos que usar aSET CONSTRAINTS
instrução para adiá-las durante a transação. Em todos os casos, porém, precisamos inserir nas tabelas em uma única transação:Testado no SQLfiddle .
Se o DBMS não tiver
DEFERRABLE
restrições, uma solução alternativa é definir as colunasA (bid)
e como . Os procedimentos/declarações terão que primeiro inserir em e (colocando nulos em e respectivamente), depois inserir e atualizar os valores nulos acima para os valores não nulos relacionados de .B (aid)
NULL
INSERT
A
B
bid
aid
R
R
Com essa abordagem, o DBMS não impõe os requisitos apenas por DDL, mas todos os procedimentos
INSERT
(eUPDATE
eDELETE
eMERGE
) devem ser considerados e ajustados de acordo e os usuários devem ser restritos a usá-los apenas e não ter acesso de gravação direto às tabelas.Ter círculos nas
FOREIGN KEY
restrições não é considerado por muitos a melhor prática e por boas razões, sendo a complexidade uma delas. Com a segunda abordagem, por exemplo (com colunas anuláveis), a atualização e exclusão de linhas ainda terão que ser feitas com código extra, dependendo do DBMS. No SQL Server, por exemplo, você não pode simplesmente colocarON DELETE CASCADE
porque atualizações e exclusões em cascata não são permitidas quando há círculos FK.Por favor, leia também as respostas nesta questão relacionada:
Como ter um relacionamento um-para-muitos com uma criança privilegiada?
Outra terceira abordagem (veja minha resposta na pergunta acima mencionada) é remover completamente os FKs circulares. Assim, mantendo a primeira parte do código (com tabelas
A
,B
,R
e chaves estrangeiras apenas de R para A e B) quase intacta (simplificando-a na verdade), adicionamos outra tabela paraA
armazenar o item relacionado "must have one" deB
. Assim, aA (bid)
coluna se move paraA_one (bid)
O mesmo é feito para a relação inversa de B para A:A diferença entre a 1ª e a 2ª abordagem é que não há FKs circulares, portanto, atualizações e exclusões em cascata funcionarão bem. A aplicação da "participação total" não é apenas por DDL, como na 2ª abordagem, e deve ser feita por procedimentos apropriados (
INSERT/UPDATE/DELETE/MERGE
). Uma pequena diferença com a segunda abordagem é que todas as colunas podem ser definidas como não anuláveis.Outra quarta abordagem (consulte a resposta de @Aaron Bertrand na pergunta acima mencionada) é usar índices exclusivos filtrados/parciais , se estiverem disponíveis em seu DBMS (você precisaria de dois deles, na
R
tabela, para este caso). Isso é muito semelhante à 3ª abordagem, exceto que você não precisará das 2 tabelas extras. A restrição de "participação total" ainda precisa ser aplicada por código.Você não pode diretamente. Para começar, você não seria capaz de inserir o registro para A sem um B já existente, mas não poderia criar o registro B se não houvesse um registro A para ele. Existem várias maneiras de aplicá-lo usando coisas como gatilhos - você teria que verificar em cada inserção e exclusão se pelo menos um registro correspondente permanece na tabela de links AB.