Ich habe eine Tabelle , containers
die eine many-to-many - Beziehungen zu mehreren Tabellen haben, lassen Sie uns sagen , die sind plants
, animals
und bacteria
. Jeder Behälter kann eine beliebige Anzahl von Pflanzen, Tieren oder Bakterien enthalten, und jede Pflanze, jedes Tier oder jedes Bakterium kann sich in einer beliebigen Anzahl von Behältern befinden.
Bisher ist dies sehr einfach, aber der Teil, mit dem ich ein Problem habe, ist, dass jeder Container nur Elemente des gleichen Typs enthalten sollte. Gemischte Behälter, die z. B. sowohl Pflanzen als auch Tiere enthalten, sollten eine Einschränkung der Datenbank darstellen.
Mein ursprüngliches Schema hierfür war das Folgende:
containers
----------
id
...
...
containers_plants
-----------------
container_id
plant_id
containers_animals
------------------
container_id
animal_id
containers_bacteria
-------------------
container_id
bacterium_id
Mit diesem Schema kann ich mir jedoch nicht vorstellen, wie die Einschränkung implementiert werden soll, dass Container homogen sein sollen.
Gibt es eine Möglichkeit, dies mit referenzieller Integrität zu implementieren und auf Datenbankebene sicherzustellen, dass die Container homogen sind?
Ich benutze dafür Postgres 9.6.
quelle
Antworten:
Es gibt eine Möglichkeit, dies nur deklarativ zu implementieren, ohne Ihr aktuelles Setup wesentlich zu ändern, wenn Sie damit einverstanden sind, eine gewisse Redundanz einzuführen. Was folgt, kann als eine Entwicklung auf RDFozz 'Vorschlag betrachtet werden , obwohl die Idee in meinem Kopf vollständig entstanden ist, bevor ich seine Antwort gelesen habe (und es ist anders genug, um einen eigenen Antwortbeitrag zu rechtfertigen).
Implementierung
Hier ist, was Sie Schritt für Schritt tun:
Erstellen Sie eine
containerTypes
Tabelle nach dem Vorbild der Antwort von RDFozz:Füllen Sie es mit vordefinierten IDs für jeden Typ. Lassen Sie sie für diese Antwort mit dem Beispiel von RDFozz übereinstimmen: 1 für Pflanzen, 2 für Tiere, 3 für Bakterien.
Fügen Sie eine
containerType_id
Spalte hinzucontainers
und machen Sie sie nicht nullbar und einen Fremdschlüssel.Angenommen, die
id
Spalte ist bereits der Primärschlüssel voncontainers
, erstellen Sie eine eindeutige Einschränkung für(id, containerType_id)
.Hier beginnen die Redundanzen. Wenn
id
es als Primärschlüssel deklariert wird, können wir sicher sein, dass es eindeutig ist. Wenn es eindeutig ist, muss jede Kombination ausid
und einer anderen Spalte auch ohne zusätzliche Erklärung der Eindeutigkeit eindeutig sein. Worum geht es also? Der Punkt ist, dass wir durch formelles Deklarieren des Spaltenpaars als eindeutig verweisbar machen , dh das Ziel einer Fremdschlüsseleinschränkung sein, worum es in diesem Teil geht.Fügen Sie eine
containerType_id
Spalte zu jedem der Kreuzung Tabellen (containers_animals
,containers_plants
,containers_bacteria
). Das Erstellen eines Fremdschlüssels ist völlig optional. Entscheidend ist, dass die Spalte für alle Zeilen den gleichen Wert hat, der für jede Tabelle unterschiedlich ist: 1 fürcontainers_plants
, 2 fürcontainers_animals
, 3 fürcontainers_bacteria
, gemäß den Beschreibungen incontainerTypes
. In jedem Fall können Sie diesen Wert auch als Standard festlegen, um Ihre Einfügeanweisungen zu vereinfachen:Machen Sie in jeder der Junction-Tabellen das Spaltenpaar zu
(container_id, containerType_id)
einer Referenz für Fremdschlüsseleinschränkungencontainers
.Wenn
container_id
bereits ein Verweis auf definiert ist, können Siecontainers
diese Einschränkung aus jeder Tabelle entfernen, wenn dies nicht mehr erforderlich ist.Wie es funktioniert
Indem Sie die Spalte Containertyp hinzufügen und an den Fremdschlüsseleinschränkungen teilnehmen lassen, bereiten Sie einen Mechanismus vor, der verhindert, dass sich der Containertyp ändert. Das Ändern des Typs im
containers
Typ wäre nur möglich, wenn die Fremdschlüssel mit derDEFERRABLE
Klausel definiert würden , die in dieser Implementierung nicht enthalten sein soll.Selbst wenn sie aufschiebbar wären, wäre eine Änderung des Typs aufgrund der
containers
Prüfbeschränkung auf der anderen Seite der Beziehung der Verbindungstabelle nicht möglich. Jede Kreuzungstabelle erlaubt nur einen bestimmten Containertyp. Das verhindert nicht nur vorhandene Referenzen aus dem Typ zu ändern , sondern auch verhindert , dass zusätzlich die falschen Referenzen. Das heißt, wenn Sie einen Container vom Typ 2 (Tiere) haben, können Sie ihm nur Elemente hinzufügen, indem Sie die Tabelle verwenden, in der Typ 2 zulässig istcontainers_animals
, dh keine Zeilen hinzufügen kann, die darauf verweisen, z. B.containers_bacteria
was akzeptiert Nur Behälter vom Typ 3.Schließlich macht es Ihre eigene Entscheidung, unterschiedliche Tabellen für
plants
,animals
undbacteria
und unterschiedliche Junction-Tabellen für jeden Entitätstyp zu haben, einem Container bereits unmöglich, Elemente von mehr als einem Typ zu haben.Alle diese Faktoren zusammen sorgen also auf rein deklarative Weise dafür, dass alle Ihre Container homogen sind.
quelle
Eine Option ist das Hinzufügen eines
containertype_id
zurContainer
Tabelle. Machen Sie die Spalte NICHT NULL und einen Fremdschlüssel für eineContainerType
Tabelle, die Einträge für jeden Elementtyp enthält, der in einen Container aufgenommen werden kann:Um sicherzustellen, dass der Containertyp nicht geändert werden kann, erstellen Sie einen Aktualisierungsauslöser, der prüft, ob der
containertype_id
aktualisiert wurde, und die Änderung in diesem Fall rückgängig macht.Überprüfen Sie dann beim Einfügen und Aktualisieren von Triggern für Ihre Containerverknüpfungstabellen die Datei includeertype_id mit dem Entitätstyp in dieser Tabelle, um sicherzustellen, dass sie übereinstimmen.
Wenn alles, was Sie in einen Container einfügen, mit dem Typ übereinstimmen muss und der Typ nicht geändert werden kann, ist alles im Container vom selben Typ.
HINWEIS: Da der Auslöser in den Verknüpfungstabellen entscheidet, welche Übereinstimmungen vorliegen, können Sie diesen Typ erstellen, dem Container zuordnen und dies überprüfen, wenn Sie einen Containertyp benötigen, der Pflanzen und Tiere enthalten kann . Sie behalten also Ihre Flexibilität, wenn sich irgendwann etwas ändert (sagen wir, Sie erhalten die Typen "Zeitschriften" und "Bücher" ...).
HINWEIS: Wenn das meiste, was mit Containern passiert, dasselbe ist, unabhängig davon, was sich in ihnen befindet, ist dies sinnvoll. Wenn Sie sehr unterschiedliche Dinge haben (im System, nicht in unserer physischen Realität), basierend auf dem Inhalt des Containers, dann ist Evan Carrolls Idee, separate Tabellen für die separaten Containertypen zu haben, durchaus sinnvoll. Diese Lösung stellt fest, dass Container beim Erstellen unterschiedliche Typen haben, diese jedoch in derselben Tabelle belassen. Wenn Sie den Typ jedes Mal überprüfen müssen, wenn Sie eine Aktion für einen Container ausführen, und wenn die Aktion, die Sie ausführen, vom Typ abhängt, sind separate Tabellen möglicherweise schneller und einfacher.
quelle
Wenn Sie nur 2 oder 3 Kategorien (Pflanzen / Metazoen / Bakterien) benötigen und eine XOR-Beziehung modellieren möchten, ist möglicherweise ein "Bogen" die Lösung für Sie. Vorteil: keine Trigger erforderlich. Beispieldiagramme finden Sie [hier] [1]. In Ihrer Situation würde die Tabelle "Container" 3 Spalten mit einer CHECK-Einschränkung enthalten, die entweder eine Pflanze, ein Tier oder ein Bakterium zulassen.
Dies ist wahrscheinlich nicht angemessen, wenn in Zukunft zwischen vielen Kategorien (z. B. Gattungen, Arten, Unterarten) unterschieden werden muss. Für 2-3 Gruppen / Kategorien kann dies jedoch den Trick tun.
UPDATE: Inspiriert von den Vorschlägen und Kommentaren des Mitwirkenden, einer anderen Lösung, die viele Taxa (Gruppen verwandter Organismen, vom Biologen klassifiziert) zulässt und "spezifische" Tabellennamen vermeidet (PostgreSQL 9.5).
DDL-Code:
Testdaten:
Testen:
Vielen Dank an @RDFozz und @Evan Carroll und @ypercube für ihren Input und ihre Geduld (Lesen / Korrigieren meiner Antworten).
quelle
Erstens stimme ich @RDFozz beim Lesen der Frage zu. Er äußert jedoch einige Bedenken hinsichtlich der Antwort von Stefans .
Um seine Bedenken auszuräumen, nur
PRIMARY KEY
UNIQUE
Einschränkungen zum Schutz vor doppelten Einträgen hinzu.EXCLUSION
Einschränkungen hinzu, um sicherzustellen, dass die Container "homogen" sind.c_id
, um eine angemessene Leistung sicherzustellen.So sieht es aus:
Jetzt können Sie einen Container mit mehreren Dingen haben, aber nur einen Typ in einem Container.
Und alles ist in GIST-Indizes implementiert.
Die Große Pyramide von Gizeh hat nichts mit PostgreSQL zu tun.
quelle
Das ist eine schlechte Idee.
Und jetzt weißt du warum. =)
Ich glaube, Sie bleiben bei der Idee der Vererbung durch objektorientierte Programmierung (OO). OO-Vererbung löst ein Problem bei der Wiederverwendung von Code. In SQL ist redundanter Code das geringste unserer Probleme. Integrität ist in erster Linie. Leistung ist oft an zweiter Stelle. Wir werden die ersten beiden Schmerzen haben. Wir haben keine "Kompilierungszeit", die die Kosten eliminieren kann.
Verzichten Sie also einfach auf Ihre Besessenheit, Code wiederzuverwenden. Behälter für Pflanzen, Tiere und Bakterien unterscheiden sich überall in der realen Welt grundlegend. Die Code-Wiederverwendungskomponente von "hold stuff" erledigt das einfach nicht für Sie. Zerbrich sie. Dies bringt Ihnen nicht nur mehr Integrität und Leistung, sondern in Zukunft fällt es Ihnen auch leichter, Ihr Schema zu erweitern: Schließlich mussten Sie in Ihrem Schema bereits die enthaltenen Elemente (Pflanzen, Tiere usw.) aufteilen. , scheint zumindest möglich, dass Sie die Behälter auseinander brechen müssen. Sie werden dann nicht Ihr gesamtes Schema neu gestalten wollen.
quelle
plant_containers
und so weiter. Dinge, die nur einen Pflanzenbehälter benötigen, werden nur aus derplant_containers
Tabelle ausgewählt. Dinge, die einen Container benötigen (dh alle Arten von Containern durchsuchen), könnenUNION ALL
für alle drei Tabellen mit Containern ausgeführt werden.