Comment associer deux lignes dans le même tableau

11

J'ai une table où les lignes peuvent être liées les unes aux autres, et logiquement, la relation va dans les deux sens (essentiellement, est sans direction) entre les deux lignes. (Et si vous vous demandez, oui, cela devrait vraiment être une seule table. Ce sont deux choses exactement de la même entité / type logique.) Je peux penser à deux façons de représenter cela:

  1. Conserver la relation et son inverse
  2. Stockez la relation dans un sens, empêchez la base de données de la stocker dans l'autre sens et ayez deux index avec des ordres opposés pour les FK (un index étant l'index PK)
  3. Stockez la relation dans un sens avec deux index et laissez le deuxième être inséré de toute façon (ça sonne un peu dégoûtant, mais bon, exhaustivité)
  4. Créez une sorte de table de regroupement et ayez un FK sur la table d'origine. (Cela soulève beaucoup de questions. La table de regroupement n'aurait qu'un nombre; pourquoi même la table? Rendre FK NULLable ou avoir des groupes avec une seule ligne associée?)

Quels sont les principaux avantages et inconvénients de ces moyens, et bien sûr, y a-t-il un moyen auquel je n'ai pas pensé?

Voici un SQLFiddle avec lequel jouer: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Il se trouve que c'est PostgreSQL car c'est ce que j'utilise, mais je ne pense pas que cette question soit très spécifique à PostgreSQL.) Il stocke actuellement à la fois la relation et son inverse à titre d'exemple.

jpmc26
la source
Une valeur donnée peut-elle être liée à plusieurs autres? Une valeur donnée est-elle toujours liée à une autre? Partagent-ils les mêmes autres données communes?
Philᵀᴹ
Oui, ils peuvent être liés à plus d'une autre ligne. Non, ils ne sont pas nécessairement toujours liés à une autre ligne. Ils n'ont pas nécessairement de données communes. Je vous remercie.
jpmc26
Oops. J'ai oublié le @Phil. Également édité pour ajouter une structure potentielle qui m'est venue à l'esprit.
jpmc26

Réponses:

9

Ce que vous avez conçu est bon. Ce qui doit être ajouté est une contrainte pour rendre la relation sans direction. Ainsi, vous ne pouvez pas avoir de (1,5)ligne sans qu'une (5,1)ligne ne soit également ajoutée.

Cela peut être accompli * avec une contrainte d'auto-référencement sur la table de bridge.

*: cela peut être accompli dans Postgres, Oracle, DB2 et tous les SGBD qui ont implémenté des contraintes de clé étrangère comme le décrit le standard SQL (différé, par exemple vérifié à la fin de la transaction.) La vérification différée n'est pas vraiment nécessaire de toute façon, comme dans SQL- Serveur qui les vérifie à la fin de l'instruction et cette construction fonctionne toujours. Vous ne pouvez pas le faire dans MySQL car "InnoDB vérifie les contraintes UNIQUE et FOREIGN KEY ligne par ligne" .

Ainsi, dans Postgres, les éléments suivants répondront à vos besoins:

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)
);

Testé sur: SQL-Fiddle

Si vous essayez d'ajouter une ligne (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Il échoue avec:

ERREUR: l'insertion ou la mise à jour sur la table "bridge_x" viole la contrainte de clé étrangère "x_x_directionless"
Détail: La clé (x_id2, x_id1) = (5, 1) n'est pas présente dans la table "bridge_x".:
INSÉRER DANS LES VALEURS bridge_x VALEURS (1,5)

De plus, vous pouvez ajouter une CHECKcontrainte si vous souhaitez interdire des (y,y)lignes:

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

Il existe d'autres façons de l'implémenter comme vous le mentionnez, comme le stockage d'une seule direction de la relation (dans une ligne, pas deux) en forçant l'ID inférieur dans x_id1et l'ID supérieur dans la x_id2colonne. Il semble plus facile à implémenter, mais conduit généralement à des requêtes plus complexes plus tard:

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ᵀᴹ
la source