Como relacionar duas linhas na mesma tabela

11

Eu tenho uma tabela em que as linhas podem ser relacionadas entre si e, logicamente, o relacionamento segue nos dois sentidos (basicamente, é sem sentido) entre as duas linhas. (E se você está se perguntando, sim, essa realmente deve ser uma tabela. São duas coisas exatamente da mesma entidade / tipo lógico.) Posso pensar em algumas maneiras de representar isso:

  1. Armazene o relacionamento e seu reverso
  2. Armazene o relacionamento de uma maneira, impeça o banco de dados de armazená-lo de outra maneira e tenha dois índices com ordens opostas para os FKs (um índice sendo o índice PK)
  3. Armazene o relacionamento de uma maneira com dois índices e permita que o segundo seja inserido de qualquer maneira (soa meio nojento, mas ei, completo)
  4. Crie algum tipo de tabela de agrupamento e tenha um FK na tabela original. (Levanta muitas perguntas. A tabela de agrupamento só teria um número; por que até a tabela? Tornar FK NULLable ou ter grupos com uma única linha associada?)

Quais são alguns dos principais prós e contras dessas formas e, é claro, existe alguma maneira em que eu não tenha pensado?

Aqui está um SQLFiddle para jogar: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Por acaso é o PostgreSQL, já que é isso que estou usando, mas não acho que essa pergunta seja muito específica do PostgreSQL.) Atualmente, ele armazena o relacionamento e seu inverso apenas como exemplo.

jpmc26
fonte
Um determinado valor pode estar relacionado a mais de um outro? Um determinado valor está sempre relacionado a outro? Eles compartilham os mesmos outros dados comuns?
Philᵀᴹ
Sim, eles podem estar relacionados a mais de uma outra linha. Não, eles nem sempre estão relacionados a outra linha. Eles não têm necessariamente nenhum dado comum. Obrigado.
jpmc26
Opa Esqueceu o @Phil. Também editado para adicionar uma estrutura em potencial que acabou de me ocorrer.
jpmc26

Respostas:

9

O que você projetou é bom. O que precisa ser adicionado é uma restrição para tornar o relacionamento sem direção. Portanto, você não pode ter uma (1,5)linha sem a (5,1)adição de uma linha.

Isso pode ser realizado * com uma restrição de auto-referência na tabela de pontes.

*: pode ser realizado no Postgres, Oracle, DB2 e todos os DBMS que implementaram restrições de chave estrangeira como o padrão SQL descreve (adiado, por exemplo, verificado no final da transação). A verificação adiada não é realmente necessária, como no SQL Servidor que os verifica no final da instrução e essa construção ainda funciona. Você não pode fazer isso no MySQL porque "O InnoDB verifica as restrições UNIQUE e FOREIGN KEY linha por linha" .

Portanto, no Postgres, o seguinte corresponderá aos seus requisitos:

CREATE TABLE x
(
  x_id SERIAL NOT NULL PRIMARY KEY,
  data VARCHAR(10) NOT NULL
);

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    FOREIGN KEY (x_id2, x_id1)
    REFERENCES bridge_x (x_id1, x_id2)
);

Testado em: SQL-Fiddle

Se você tentar adicionar uma linha (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Falha com:

ERRO: inserir ou atualizar na tabela "bridge_x" viola a restrição de chave estrangeira "x_x_directionless"
Detalhe: Chave (x_id2, x_id1) = (5, 1) não está presente na tabela "bridge_x" .:
INSERT INTO bridge_x VALUES (1,5)

Além disso, você pode adicionar uma CHECKrestrição se quiser proibir (y,y)linhas:

ALTER TABLE bridge_x
  ADD CONSTRAINT x_x_self_referencing_items_not_allowed
    CHECK (x_id1 <> x_id2) ;

Existem outras maneiras de implementar isso, como você menciona, como armazenar apenas uma direção do relacionamento (em uma linha, não em duas) forçando o ID mais baixo x_id1e o ID mais alto na x_id2coluna. Parece mais fácil de implementar, mas geralmente leva a consultas mais complexas posteriormente:

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    CHECK (x_id1 <= x_id2)                       -- or "<" to forbid `(y,y)` rows
);
ypercubeᵀᴹ
fonte