In SQL ist das nicht einfach, aber nicht unmöglich. Wenn dies nur durch DDL erzwungen werden soll, muss das DBMS DEFERRABLE
Einschränkungen implementiert haben . Dies könnte geschehen (und kann überprüft werden, um in Postgres zu funktionieren, das sie implementiert hat):
-- lets create first the 2 tables, A and B:
CREATE TABLE a
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
Bis hierher gibt es das "normale" Design, bei dem jedes A
auf Null, eins oder viele B
und jedes B
auf Null, eins oder viele bezogen werden kann A
.
Die „Gesamt Teilnahme“ Beschränkung muss Einschränkungen in der umgekehrten Reihenfolge (von A
und B
bzw. Referenzierung R
). Mit FOREIGN KEY
Einschränkungen in entgegengesetzten Richtungen (von X nach Y und von Y bis X) einen Kreises (eine „Huhn und Ei“ -Problem) , und dass die Bildung , warum wir eine von ihnen zumindest sein müssen DEFERRABLE
. In diesem Fall haben wir zwei Kreise ( A -> R -> A
und B -> R -> B
daher brauchen wir zwei aufschiebbare Bedingungen:
-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
ALTER TABLE b
ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
Dann können wir testen, ob wir Daten einfügen können. Beachten Sie, dass das INITIALLY DEFERRED
nicht benötigt wird. Wir hätten die Einschränkungen definieren können als, DEFERRABLE INITIALLY IMMEDIATE
aber dann müssten wir die SET CONSTRAINTS
Anweisung verwenden, um sie während der Transaktion aufzuschieben. In jedem Fall müssen wir jedoch in einer einzigen Transaktion in die Tabellen einfügen:
-- insert data
BEGIN TRANSACTION ;
INSERT INTO a (aid, bid)
VALUES
(1, 1), (2, 5),
(3, 7), (4, 1) ;
INSERT INTO b (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7) ;
INSERT INTO r (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7), (4, 1),
(4, 2), (4, 7) ;
END ;
Getestet bei SQLfiddle .
Wenn das DBMS keine DEFERRABLE
Einschränkungen aufweist, besteht eine Problemumgehung darin, die Spalten A (bid)
und B (aid)
als zu definieren NULL
. Die INSERT
Verfahren / Anweisungen werden dann zum ersten Einsatz haben in A
und B
(NULL - Werten bei der Umsetzung bid
und aid
respectively), dann in einfügen R
und dann über den Zusammenhang keine Nullwerte aus den Nullwert aktualisieren R
.
Bei diesem Ansatz erzwingt das DBMS nicht nur die Anforderungen von DDL, sondern es muss jede INSERT
(und UPDATE
und DELETE
und MERGE
) Prozedur entsprechend berücksichtigt und angepasst werden, und Benutzer müssen darauf beschränkt werden, nur diese zu verwenden, und haben keinen direkten Schreibzugriff auf die Tabellen.
Kreise in den FOREIGN KEY
Beschränkungen zu haben, wird von vielen nicht als die beste Praxis angesehen, und aus guten Gründen ist die Komplexität eine davon. Beim zweiten Ansatz (z. B. mit nullwertfähigen Spalten) muss das Aktualisieren und Löschen von Zeilen je nach DBMS mit zusätzlichem Code ausgeführt werden. In SQL Server zum Beispiel können Sie nicht einfach setzen, ON DELETE CASCADE
da kaskadierende Aktualisierungen und Löschvorgänge nicht zulässig sind, wenn FK-Kreise vorhanden sind.
Bitte lesen Sie auch die Antworten zu dieser verwandten Frage:
Wie kann man eine Eins-zu-Viele-Beziehung zu einem privilegierten Kind haben?
Ein anderer, dritter Ansatz (siehe meine Antwort in der oben genannten Frage) besteht darin, die kreisförmigen FKs vollständig zu entfernen. So, den ersten Teil des Codes zu halten (mit Tabellen A
, B
, R
und Fremdschlüssel nur von R A und B) fast vollständig erhalten (vereinfacht es tatsächlich), fügen wir eine weitere Tabelle für A
die zum Speichern von „muss man hat“ verwandten Artikel B
. Die A (bid)
Spalte verschiebt sich also zu. A_one (bid)
Dasselbe gilt für die umgekehrte Beziehung von B zu A:
CREATE TABLE a
( aid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
CREATE TABLE a_one
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_one_pk PRIMARY KEY (aid),
CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
);
CREATE TABLE b_one
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_one_pk PRIMARY KEY (bid),
CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
);
Der Unterschied zum 1. und 2. Ansatz besteht darin, dass es keine zirkulären FKs gibt, sodass kaskadierende Aktualisierungen und Löschvorgänge problemlos funktionieren. Die Durchsetzung der "vollständigen Beteiligung" erfolgt nicht wie im zweiten Ansatz allein durch DDL und muss durch geeignete Verfahren erfolgen ( INSERT/UPDATE/DELETE/MERGE
). Ein kleiner Unterschied zum 2. Ansatz besteht darin, dass alle Spalten nicht nullwertfähig definiert werden können.
Ein anderer, vierter Ansatz (siehe @Aaron Bertrands Antwort in der oben genannten Frage) besteht darin, gefilterte / teilweise eindeutige Indizes zu verwenden, sofern diese in Ihrem DBMS verfügbar sind ( R
für diesen Fall benötigen Sie zwei davon in der Tabelle). Dies ist dem 3. Ansatz sehr ähnlich, außer dass Sie die 2 zusätzlichen Tabellen nicht benötigen. Die Einschränkung "Gesamtteilnahme" muss noch per Code angewendet werden.