So verknüpfen Sie zwei Zeilen in derselben Tabelle

11

Ich habe eine Tabelle, in der die Zeilen miteinander in Beziehung gesetzt werden können, und logischerweise verläuft die Beziehung zwischen den beiden Zeilen in beide Richtungen (im Grunde genommen richtungslos). (Und wenn Sie sich fragen, ja, dies sollte wirklich eine Tabelle sein. Es sind zwei Dinge von genau derselben logischen Entität / demselben logischen Typ.) Ich kann mir ein paar Möglichkeiten vorstellen, dies darzustellen:

  1. Speichern Sie die Beziehung und ihre Umkehrung
  2. Speichern Sie die Beziehung in eine Richtung, beschränken Sie die Datenbank daran, sie in die andere Richtung zu speichern, und haben Sie zwei Indizes mit entgegengesetzter Reihenfolge für die FKs (ein Index ist der PK-Index).
  3. Speichern Sie die Beziehung auf eine Weise mit zwei Indizes und lassen Sie die zweite trotzdem einfügen (klingt irgendwie glücklich, aber hey, Vollständigkeit)
  4. Erstellen Sie eine Art Gruppierungstabelle und haben Sie eine FK in der Originaltabelle. (Wirft viele Fragen auf. Die Gruppierungstabelle würde nur eine Zahl haben. Warum überhaupt die Tabelle? FK NULLable machen oder Gruppen mit einer einzelnen Zeile zuordnen?)

Was sind einige wichtige Vor- und Nachteile dieser Wege, und gibt es natürlich einen Weg, an den ich nicht gedacht habe?

Hier ist eine SQLFiddle zum Spielen: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Scheint PostgreSQL zu sein, da ich das verwende, aber ich denke nicht, dass diese Frage sehr spezifisch für PostgreSQL ist.) Derzeit werden sowohl die Beziehung als auch die Umkehrung nur als Beispiel gespeichert.

jpmc26
quelle
Kann ein bestimmter Wert mit mehr als einem anderen in Beziehung gesetzt werden? Ist ein bestimmter Wert immer mit einem anderen verbunden? Teilen sie dieselben anderen gemeinsamen Daten?
Philᵀᴹ
Ja, sie können mit mehr als einer anderen Zeile verknüpft sein. Nein, sie sind nicht unbedingt immer mit einer anderen Zeile verbunden. Sie haben nicht unbedingt gemeinsame Daten. Vielen Dank.
jpmc26
Hoppla. Ich habe das @Phil vergessen. Wird auch bearbeitet, um eine potenzielle Struktur hinzuzufügen, die mir gerade eingefallen ist.
jpmc26

Antworten:

9

Was Sie entworfen haben, ist gut. Was hinzugefügt werden muss, ist eine Einschränkung, um die Beziehung richtungslos zu machen. Sie können also keine (1,5)Zeile haben, ohne dass auch eine (5,1)Zeile hinzugefügt wird.

Dies kann * mit einer selbstreferenzierenden Einschränkung für die Brückentabelle erreicht werden.

*: Dies kann in Postgres, Oracle, DB2 und allen DBMS durchgeführt werden, die Fremdschlüsseleinschränkungen implementiert haben, wie im SQL-Standard beschrieben (verzögert, z. B. am Ende der Transaktion überprüft). Eine verzögerte Überprüfung ist ohnehin nicht wirklich erforderlich, wie in SQL- Server, der sie am Ende der Anweisung überprüft und diese Konstruktion funktioniert weiterhin. Sie können dies in MySQL nicht tun, da "InnoDB die Einschränkungen für EINZIGARTIGE und AUSLÄNDISCHE SCHLÜSSEL zeilenweise überprüft" .

In Postgres entspricht Folgendes Ihren Anforderungen:

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

Getestet bei: SQL-Fiddle

Wenn Sie versuchen, eine Zeile hinzuzufügen (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Es schlägt fehl mit:

FEHLER: Einfügen oder Aktualisieren in Tabelle "bridge_x" verstößt gegen Fremdschlüsseleinschränkung "x_x_directionless"
Detail: Schlüssel (x_id2, x_id1) = (5, 1) ist in Tabelle "bridge_x" nicht vorhanden.:
INSERT INbridge_x VALUES (1,5)

Darüber hinaus können Sie eine CHECKEinschränkung hinzufügen , wenn Sie (y,y)Zeilen verbieten möchten :

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

Wie Sie bereits erwähnt haben, gibt es andere Möglichkeiten, dies zu implementieren, z. B. das Speichern nur einer Richtung der Beziehung (in einer Zeile, nicht in zwei), indem die niedrigere ID x_id1und die höhere ID in der x_id2Spalte erzwungen werden. Es sieht einfacher zu implementieren aus, führt aber normalerweise später zu komplexeren Abfragen:

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ᵀᴹ
quelle