Angenommen, wir haben eine Tabelle, für die eine Fremdschlüsseleinschränkung gilt:
CREATE TABLE Foo
(FooId BIGINT PRIMARY KEY,
ParentFooId BIGINT,
FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )
INSERT INTO Foo (FooId, ParentFooId)
VALUES (1, NULL), (2, 1), (3, 2)
UPDATE Foo SET ParentFooId = 3 WHERE FooId = 1
Diese Tabelle enthält die folgenden Datensätze:
FooId ParentFooId
----- -----------
1 3
2 1
3 2
Es gibt Fälle, in denen diese Art von Design sinnvoll sein könnte (z. B. die typische Beziehung "Mitarbeiter und Chef-Mitarbeiter"), und auf jeden Fall: Ich bin in einer Situation, in der ich dies in meinem Schema habe.
Diese Art von Design ermöglicht leider eine Zirkularität in Datensätzen, wie im obigen Beispiel gezeigt.
Meine Frage ist dann:
- Ist es möglich , eine Einschränkung zu schreiben, die dies überprüft? und
- Ist es möglich , eine Einschränkung zu schreiben, die dies überprüft? (bei Bedarf nur bis zu einer bestimmten Tiefe)
Für Teil (2) dieser Frage kann es relevant sein zu erwähnen, dass ich nur Hunderte oder in einigen Fällen Tausende von Datensätzen in meiner Tabelle erwarte, die normalerweise nicht tiefer als etwa 5 bis 10 Ebenen verschachtelt sind.
PS. MS SQL Server 2008
Update 14. März 2012
Es gab mehrere gute Antworten. Ich habe jetzt die akzeptiert, die mir geholfen hat , die erwähnte Möglichkeit / Machbarkeit zu verstehen . Es gibt jedoch noch einige andere gute Antworten, einige auch mit Implementierungsvorschlägen. Wenn Sie also mit derselben Frage hier gelandet sind, sehen Sie sich alle Antworten an;)
quelle
HIERARCHYID
denen eine native MSSQL2008-Implementierung des verschachtelten Mengenmodells zu gehören scheint.Ich habe zwei Hauptmethoden gesehen, um dies durchzusetzen:
1, der alte Weg:
Die Spalte FooHierarchy würde einen Wert wie den folgenden enthalten:
Wo die Zahlen der FooId-Spalte zugeordnet sind. Sie würden dann erzwingen, dass die Hierarchiespalte mit "| id" endet und der Rest der Zeichenfolge mit der FooHieratchy der PARENT übereinstimmt.
2, der NEUE Weg:
SQL Server 2008 verfügt über einen neuen Datentyp namens HierarchyID , der all dies für Sie erledigt.
Es arbeitet nach dem gleichen Prinzip wie OLD, wird jedoch von SQL Server effizient verwaltet und eignet sich als ERSATZ für Ihre Spalte "ParentID".
quelle
HIERARCHYID
die die Erstellung von Hierarchieschleifen verhindert?Es ist irgendwie möglich: Sie können eine skalare UDF aus Ihrer CHECK-Einschränkung aufrufen und Zyklen beliebiger Länge erkennen. Leider ist dieser Ansatz extrem langsam und unzuverlässig: Sie können falsch positive und falsch negative Ergebnisse erzielen.
Stattdessen würde ich einen materialisierten Pfad verwenden.
Eine andere Möglichkeit, Zyklen zu vermeiden, besteht darin, einen CHECK (ID> ParentID) zu haben, was wahrscheinlich auch nicht sehr machbar ist.
Eine weitere Möglichkeit, Zyklen zu vermeiden, besteht darin, zwei weitere Spalten hinzuzufügen, LevelInHierarchy und ParentLevelInHierarchy, auf die (ParentID, ParentLevelInHierarchy) verweisen (ID, LevelInHierarchy) und einen CHECK (LevelInHierarchy> ParentLevelInHierarchy).
quelle
Ich glaube es ist möglich:
Ich habe vielleicht etwas verpasst (sorry, ich kann es nicht gründlich testen), aber es scheint zu funktionieren.
quelle
Hier ist eine weitere Option: Ein Trigger, der mehrzeilige Aktualisierungen ermöglicht und keine Zyklen erzwingt. Es durchläuft die Ahnenkette, bis ein Stammelement (mit dem übergeordneten NULL) gefunden wird, wodurch bewiesen wird, dass kein Zyklus vorhanden ist. Es ist auf 10 Generationen begrenzt, da ein Zyklus natürlich endlos ist.
Es funktioniert nur mit dem aktuellen Satz geänderter Zeilen. Solange Aktualisierungen nicht eine große Anzahl sehr tiefer Elemente in der Tabelle berühren, sollte die Leistung nicht zu schlecht sein. Es muss für jedes Element die gesamte Kette hinaufgehen, damit es einige Auswirkungen auf die Leistung hat.
Ein wirklich "intelligenter" Auslöser würde direkt nach Zyklen suchen, indem er prüft, ob ein Gegenstand sich selbst erreicht hat, und dann auf Kaution geht. Dies erfordert jedoch die Überprüfung des Status aller zuvor gefundenen Knoten während jeder Schleife und erfordert daher eine WHILE-Schleife und mehr Codierung, als ich gerade wollte. Dies sollte eigentlich nicht teurer sein, da der normale Betrieb darin besteht, keine Zyklen zu haben. In diesem Fall ist es schneller, nur mit der vorherigen Generation zu arbeiten, als mit allen vorherigen Knoten während jeder Schleife.
Ich würde gerne Beiträge von @AlexKuznetsov oder anderen dazu erhalten, wie sich dies bei der Isolierung von Schnappschüssen auswirken würde. Ich vermute, es wäre nicht sehr gut, würde es aber gerne besser verstehen.
Aktualisieren
Ich habe herausgefunden, wie ich eine zusätzliche Verknüpfung mit der eingefügten Tabelle vermeiden kann. Wenn jemand einen besseren Weg sieht, die GROUP BY durchzuführen, um diejenigen zu erkennen, die kein NULL enthalten, lassen Sie es mich bitte wissen.
Ich habe auch einen Schalter zu READ COMMITTED hinzugefügt, wenn sich die aktuelle Sitzung in der Stufe SNAPSHOT ISOLATION befindet. Dies verhindert Inkonsistenzen, führt jedoch leider zu einer erhöhten Blockierung. Das ist für die anstehende Aufgabe unvermeidlich.
quelle
Wenn Ihre Datensätze mehr als eine Ebene verschachtelt sind, funktioniert eine Einschränkung nicht (ich gehe davon aus, dass Sie damit meinen, dass Datensatz 1 das übergeordnete Element von Datensatz 2 und Datensatz 3 das übergeordnete Element von Datensatz 1 ist). Die einzige Möglichkeit, dies zu tun, wäre entweder im übergeordneten Code oder mit einem Auslöser. Wenn Sie sich jedoch eine große Tabelle und mehrere Ebenen ansehen, wäre dies ziemlich intensiv.
quelle