it-swarm.com.de

Wie kann man eine Eins-zu-Viele-Beziehung zu einem privilegierten Kind haben?

Ich möchte eine Eins-zu-Viele-Beziehung haben, in der für jeden Elternteil ein oder null der Kinder als „Favorit“ markiert ist. Allerdings wird nicht jeder Elternteil ein Kind haben. (Stellen Sie sich die Eltern als Fragen auf dieser Website vor, Kinder als Antworten und Favoriten als akzeptierte Antwort.) Zum Beispiel:

TableA
    Id            INT PRIMARY KEY

TableB
    Id            INT PRIMARY KEY
    Parent        INT NOT NULL FOREIGN KEY REFERENCES TableA.Id

So wie ich es sehe, kann ich Tabelle A entweder die folgende Spalte hinzufügen:

    FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id

oder die folgende Spalte zu Tabelle B:

    IsFavorite    BIT NOT NULL

Das Problem beim ersten Ansatz ist, dass ein nullbarer Fremdschlüssel eingeführt wird, der meines Wissens nicht in normalisierter Form vorliegt. Das Problem beim zweiten Ansatz ist, dass mehr Arbeit geleistet werden muss, um sicherzustellen, dass höchstens ein Kind der Favorit ist.

Welche Kriterien sollte ich verwenden, um zu bestimmen, welcher Ansatz verwendet werden soll? Oder gibt es andere Ansätze, die ich nicht in Betracht ziehe?

Ich verwende SQL Server 2012.

22
cubetwo1729

Eine andere Möglichkeit (ohne Nullen und ohne Zyklen in den FOREIGN KEY - Beziehungen) besteht darin, eine dritte Tabelle zum Speichern der "Lieblingskinder" zu haben. In den meisten DBMS benötigen Sie eine zusätzliche UNIQUE -Einschränkung für TableB.

@Aaron konnte schneller feststellen, dass die oben genannte Namenskonvention ziemlich umständlich ist und zu Fehlern führen kann. Es ist normalerweise besser (und hält Sie gesund), wenn Sie nicht Id Spalten in allen Tabellen haben und wenn die Spalten (die verbunden sind) in den vielen angezeigten Tabellen dieselben Namen haben. Also, hier ist eine Umbenennung:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    UNIQUE (ParentID, ChildID)

FavoriteChild
    ParentID        INT NOT NULL PRIMARY KEY
    ChildID         INT NOT NULL 
    FOREIGN KEY (ParentID, ChildID) 
        REFERENCES Child (ParentID, ChildID)

In SQL-Server (den Sie verwenden) haben Sie auch die Option der von Ihnen erwähnten Bit-Spalte IsFavorite. Das eindeutige Lieblingskind pro Elternteil kann über einen gefilterten eindeutigen Index erreicht werden:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    IsFavorite      BIT NOT NULL

CREATE UNIQUE INDEX is_FavoriteChild
  ON Child (ParentID)
  WHERE IsFavorite = 1 ;

Der Hauptgrund dafür, dass Ihre Option 1 nicht empfohlen wird, zumindest nicht in SQL Server, ist, dass das Muster der Kreispfade in den Fremdschlüsselreferenzen einige Probleme aufweist.

Lesen Sie einen ziemlich alten Artikel: SQL By Design: The Circular Reference

Wenn Sie Zeilen aus den beiden Tabellen einfügen oder löschen, tritt das Problem "Huhn und Ei" auf. Welche Tabelle soll ich zuerst einfügen - ohne eine Einschränkung zu verletzen?

Um dies zu lösen, müssen Sie mindestens eine Spalte definieren, die nullbar ist. (OK, technisch gesehen müssen Sie nicht, Sie können alle Spalten als NOT NULL Haben, aber nur in DBMS wie Postgres und Oracle, die aufschiebbare Einschränkungen implementiert haben. Siehe @ Erwins Antwort in einer ähnlichen Frage: Komplexe Fremdschlüsseleinschränkung in SQLAlchemy, wie dies in Postgres durchgeführt werden kann). Trotzdem fühlt sich dieses Setup wie Skaten auf dünnem Eis an.

Überprüfen Sie auch eine fast identische Frage unter SO (aber für MySQL) Ist es in SQL in Ordnung, wenn zwei Tabellen aufeinander verweisen? wobei meine Antwort ziemlich gleich ist. MySQL hat jedoch keine Teilindizes, daher sind die einzigen realisierbaren Optionen die nullfähige FK und die zusätzliche Tabellenlösung.

19
ypercubeᵀᴹ

Es hängt davon ab, welche Priorität Sie haben. Möchten Sie Arbeit vermeiden oder die strengsten Normalisierungsregeln einhalten?

Persönlich denke ich, dass es besser ist, IsFavorite in der untergeordneten Tabelle zu haben, und wäre bereit, die Arbeit zu investieren, um sicherzustellen, dass höchstens ein Kind für jeden Elternteil der Favorit des Elternteils ist. Der Hauptgrund hat nichts mit dem nullbaren Fremdschlüssel zu tun: Ich mag die Idee überhaupt nicht, dass Fremdschlüssel in beide Richtungen zeigen.

Der Vorschlag von @ ypercube ist ebenfalls ein guter Kompromiss.

Abgesehen davon, bitte, bitte, bitte verunreinigen Sie Ihr Schema nicht mit bedeutungslosen Spaltennamen wie Id. Ich würde viel lieber sehen, dass das Id im gesamten Schema auf sinnvolle Weise benannt wird. Identifiziert es einen Autor? Ok, nenne es AuthorID. Stellt es ein Produkt dar? Ok, ProductID. Ist es ein Mitarbeiter und verweist in einigen Fällen auf einen Manager? Ok, EmployeeID und ManagerID sind für mich sinnvoller als ID und Parent. Während es logisch erscheint, dies wegzulassen (und überflüssig, es einzufügen), werden Sie beim Schreiben komplexer Verknüpfungen (oder beim Posten von Abfragen hier) definitiv ein Fluchen spüren, wenn Sie versuchen, eine Reihe von Verknüpfungen an diesem Punkt zurückzuentwickeln zu Spalten wie a.Parent = b.ID ... blecch.

9
Aaron Bertrand

Daten gehören in die untergeordnete Tabelle. Wir halten es mit einem Auslöser auf dem Tisch korrekt, um sicherzustellen, dass ein einziger Datensatz als Favorit (oder in unserem Fall als bevorzugte Adresse) markiert ist.

Die Idee von @ ypercube, eine separate Tabelle zu erstellen, ist jedoch ebenfalls gut.

1
HLGEM