Gibt es DBMS, die einen Fremdschlüssel zulassen, der auf eine Ansicht verweist (und nicht nur auf Basistabellen)?

22

Inspiriert von einer Django-Modellierungsfrage: Datenbankmodellierung mit mehreren Viele-zu-Viele-Beziehungen in Django . Das DB-Design ist so etwas wie:

CREATE TABLE Book
( BookID INT NOT NULL
, BookTitle VARCHAR(200) NOT NULL
, PRIMARY KEY (BookID)
) ;

CREATE TABLE Tag
( TagID INT NOT NULL
, TagName VARCHAR(50) NOT NULL
, PRIMARY KEY (TagID)
) ;

CREATE TABLE BookTag
( BookID INT NOT NULL
, TagID INT NOT NULL
, PRIMARY KEY (BookID, TagID)
, FOREIGN KEY (BookID)  REFERENCES Book (BookID)
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
) ;

CREATE TABLE Aspect
( AspectID INT NOT NULL
, AspectName VARCHAR(50) NOT NULL
, PRIMARY KEY (AspectID)
) ;

CREATE TABLE TagAspect
( TagID INT NOT NULL
, AspectID INT NOT NULL
, PRIMARY KEY (TagID, AspectID) 
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
, FOREIGN KEY (AspectID)  REFERENCES Aspect (AspectID)
) ;

DB-Diagramm

und das Problem ist, wie die BookAspectRatingTabelle definiert und die referenzielle Integrität erzwungen wird, sodass für eine (Book, Aspect)ungültige Kombination keine Bewertung hinzugefügt werden kann .

AFAIK, komplexe CHECKEinschränkungen (oder ASSERTIONS), die Unterabfragen und mehr als eine Tabelle umfassen, die dies möglicherweise lösen könnten, sind in keinem DBMS verfügbar.

Eine andere Idee ist, eine Ansicht zu verwenden (Pseudocode):

CREATE VIEW BookAspect_view
  AS
SELECT DISTINCT
    bt.BookId
  , ta.AspectId
FROM 
    BookTag AS bt
  JOIN 
    Tag AS t  ON t.TagID = bt.TagID
  JOIN 
    TagAspect AS ta  ON ta.TagID = bt.TagID 
WITH PRIMARY KEY (BookId, AspectId) ;

und eine Tabelle mit einem Fremdschlüssel für die obige Ansicht:

CREATE TABLE BookAspectRating
( BookID INT NOT NULL
, AspectID INT NOT NULL
, PersonID INT NOT NULL
, Rating INT NOT NULL
, PRIMARY KEY (BookID, AspectID, PersonID)
, FOREIGN KEY (PersonID)   REFERENCES Person (PersonID)
, FOREIGN KEY (BookID, AspectID) 
    REFERENCES BookAspect_view (BookID, AspectID)
) ;

Drei Fragen:

  • Gibt es DBMS, die ein (eventuell materialisiertes) VIEWmit a erlauben PRIMARY KEY?

  • Gibt es DBMS, die ein, FOREIGN KEYdass REFERENCESein VIEW(und nicht nur eine Basis TABLE) ermöglichen?

  • Könnte dieses Integritätsproblem anders gelöst werden - mit verfügbaren DBMS-Funktionen?


Klärung:

Da gibt es wohl keine 100% befriedigende Lösung - und die Django-Frage ist nicht mal meine! - Ich bin mehr an einer allgemeinen Strategie für einen möglichen Angriff auf das Problem interessiert, nicht an einer detaillierten Lösung. Eine Antwort wie "in DBMS-X kann dies mit Triggern in Tabelle A erfolgen" ist daher durchaus akzeptabel.

ypercubeᵀᴹ
quelle
Das Posten als Kommentar zu Ihren ersten beiden Fragen - und nicht unbedingt für Sie, wie Sie sicher bereits wissen -, aber SQL Server unterstützt keine Primär- oder Fremdschlüssel für Ansichten.
Aaron Bertrand
@ Aaron: Ja, danke. Ich habe gelesen, dass Oracle PK Costraints in Ansichten unterstützt. Aber nicht sicher, ob es in dieser Situation funktionieren würde. Und die Antwort auf die 2. Frage (über FKs zu Views) ist bei Oracle wohl negativ.
ypercubeᵀᴹ
Aber ich bin interessiert zu erfahren, ob es eine andere Lösung gibt (Trigger, Check costraints oder andere Combo)
ypercubeᵀᴹ

Antworten:

9

Diese Geschäftsregel kann im Modell nur mit Einschränkungen erzwungen werden. Die folgende Tabelle sollte Ihr Problem lösen. Verwenden Sie es anstelle Ihrer Ansicht:

    CREATE TABLE BookAspectCommonTagLink
    (  BookID INT NOT NULL
    , AspectID INT NOT NULL
    , TagID INT NOT NULL
--TagID is deliberately left out of PK
    , PRIMARY KEY (BookID, AspectID)
    , FOREIGN KEY (BookID, TagID) 
        REFERENCES BookTag (BookID, TagID)
    , FOREIGN KEY (AspectID, TagID) 
        REFERENCES AspectTag (AspectID, TagID)
    ) ;
AK
quelle
Oh schön. Das einzige Problem, das ich mir vorstellen kann, ist die Komplexität beim Einfügen / Löschen von BookTags und TagAspects. Jedes Mal, wenn beispielsweise ein neues BookTag (oder TagAspect) entfernt wird, muss eine Suche durchgeführt werden, um die entsprechenden Zeilen in dieser Tabelle zu entfernen und / oder das TagIDTag in ein anderes zu ändern, das sich auf dieselbe BookAspect-Kombination bezieht.
ypercubeᵀᴹ
Und für das Einfügen in diese beiden Tabellen müsste eine ähnliche Suche durchgeführt werden. Komplexe Regeln erfordern jedoch komplexe Prozeduren, sodass dies sehr gut aussieht.
ypercubeᵀᴹ
@ypercube Wenn Sie ein Tag löschen, müssen Sie überprüfen und möglicherweise zu einem anderen Tag wechseln, der dasselbe Buch und denselben Aspekt verknüpft. Wenn Sie neue Tags einfügen, müssen Sie jedoch erst dann eine Überprüfung durchführen, wenn Sie eine Bewertung einfügen müssen.
AK
1
Stellen Sie sicher, dass die Person für die Problembehandlung und die Dateneingabe dieselbe Person ist oder dass Sie die Fehlermeldung dem Endbenutzer anzeigen. Sie denken zu viel über Ein-Personen-Läden nach, in denen Sie alles tun.
Aaron Bertrand
4
@ AaronBertrand Du hast mir gerade einen großen Gefallen getan. Ich beende einen Artikel mit dem Titel "Entwickeln wartungsarmer Datenbanken" und vergesse zu erwähnen, dass App-Server die ursprünglichen Fehlermeldungen aus Datenbanken protokollieren müssen. Ich habe es gerade hinzugefügt. Vielen Dank für die Erinnerung;)
AK
8

Ich denke, Sie werden feststellen, dass komplexe Geschäftsregeln in vielen Fällen nicht über das Modell allein durchgesetzt werden können. Dies ist einer der Fälle, in denen, zumindest in SQL Server, ein Auslöser (vorzugsweise ein statt eines Auslösers) Ihrer Meinung nach besser zu Ihrem Zweck passt.

Aaron Bertrand
quelle
Hey Aaron, kannst du bitte erklären, warum in diesem Fall ein Trigger eine bessere Wahl ist als eine Entität und ein paar Einschränkungen?
AK
2
@AlexKuznetsov Sicher, weil ich nicht 17 Stunden damit verbracht habe, darüber nachzudenken, wie dies mit mehreren mehrspaltigen Fremdschlüsseln implementiert werden soll, und all die zusätzliche Logik, die möglicherweise erforderlich ist, um die Validierung und die Fehlerbehandlung zu erledigen?
Aaron Bertrand
2
Seien Sie vorsichtig mit den Rennbedingungen, die eine naive Implementierung von Triggern mit sich bringen könnte. Beispielsweise könnte eine Transaktion ein Buch vom Tag trennen und eine andere hält es weiterhin für in Ordnung, eine Verbindung zu dem entsprechenden Aspekt herzustellen, einfach weil die erste Transaktion noch nicht festgeschrieben wurde. Die Komplexität, die durch die Antwort von @AlexKuznetsov eingeführt wird, ist wahrscheinlich geringer als die Komplexität und Fragilität des Sperr- "Protokolls", das erforderlich ist, um Race Conditions in Triggern zu verhindern, IMHO.
Branko Dimitrijevic
8

In Oracle besteht eine Möglichkeit, diese Art von Einschränkung deklarativ zu erzwingen, darin, eine materialisierte Ansicht zu erstellen, die beim Festschreiben schnell aktualisiert wird und deren Abfrage alle ungültigen Zeilen identifiziert (dh BookAspectRatingZeilen, in denen keine Übereinstimmung besteht BookAspect_view). Sie können dann eine einfache Einschränkung für diese materialisierte Ansicht erstellen, die verletzt würde, wenn die materialisierte Ansicht Zeilen enthält. Dies hat den Vorteil, dass die Datenmenge, die Sie in der materialisierten Ansicht duplizieren müssen, minimiert wird. Dies kann jedoch zu Problemen führen, da die Einschränkung nur zu dem Zeitpunkt erzwungen wird, an dem Sie die Transaktion festschreiben. Viele Anwendungen sind nicht so geschrieben, dass ein Festschreibevorgang fehlschlagen könnte. Außerdem kann die Einschränkungsverletzung etwas schwerwiegend sein einer bestimmten Zeile oder einer bestimmten Tabelle zuordnen.

Justin Cave
quelle
4

SIRA_PRISE erlaubt das.

Obwohl die FK nicht mehr "FK" heißt, sondern nur noch "Datenbankeinschränkung" und die "Ansicht" nicht einmal mehr als Ansicht definiert werden muss, können Sie die Ansicht, die den Ausdruck definiert, einfach in die Deklaration der einschließen Datenbankeinschränkung.

Ihre Einschränkung würde ungefähr so ​​aussehen

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

und du bist fertig.

In den meisten SQL-DBMS müssen Sie jedoch die Analysearbeit für Ihre Einschränkung ausführen, bestimmen, wie gegen sie verstoßen werden kann, und alle erforderlichen Trigger implementieren.

Erwin Smout
quelle
Ich kenne. Es spiegelt wider, was ich zum Zeitpunkt des Schreibens für wichtig hielt.
Erwin Smout
3

In PostgreSQL kann ich mir keine Lösung ohne Trigger vorstellen, aber sie kann sicherlich auf diese Weise gelöst werden (sei es eine materialisierte Ansicht oder ein vorheriger Trigger BookAspectRating). Es gibt keine Fremdschlüssel, die auf eine Ansicht verweisen ( ERROR: referenced relation "v_munkalap" is not a table), geschweige denn einen Primärschlüssel.

dezso
quelle