Was Sie beschreiben, nennt man polymorphe Assoziationen. Das heißt, die Spalte "Fremdschlüssel" enthält einen ID-Wert, der in einer Reihe von Zieltabellen vorhanden sein muss. In der Regel sind die Zieltabellen in irgendeiner Weise miteinander verbunden, z. B. als Instanzen einer allgemeinen Superklasse von Daten. Sie benötigen außerdem eine weitere Spalte neben der Fremdschlüsselspalte, damit Sie in jeder Zeile angeben können, auf welche Zieltabelle verwiesen wird.
CREATE TABLE popular_places (
user_id INT NOT NULL,
place_id INT NOT NULL,
place_type VARCHAR(10) -- either 'states' or 'countries'
-- foreign key is not possible
);
Es gibt keine Möglichkeit, polymorphe Assoziationen mithilfe von SQL-Einschränkungen zu modellieren. Eine Fremdschlüsseleinschränkung verweist immer auf eine Zieltabelle.
Polymorphe Assoziationen werden von Frameworks wie Rails und Hibernate unterstützt. Sie sagen jedoch ausdrücklich, dass Sie SQL-Einschränkungen deaktivieren müssen, um diese Funktion verwenden zu können. Stattdessen muss die Anwendung oder das Framework gleichwertige Arbeit leisten, um sicherzustellen, dass die Referenz erfüllt ist. Das heißt, der Wert im Fremdschlüssel ist in einer der möglichen Zieltabellen vorhanden.
Polymorphe Assoziationen sind im Hinblick auf die Durchsetzung der Datenbankkonsistenz schwach. Die Datenintegrität hängt davon ab, dass alle Clients mit derselben erzwungenen referenziellen Integritätslogik auf die Datenbank zugreifen. Außerdem muss die Durchsetzung fehlerfrei sein.
Hier sind einige alternative Lösungen, die die datenbankgesteuerte referenzielle Integrität nutzen:
Erstellen Sie eine zusätzliche Tabelle pro Ziel. Beispielsweise popular_states
und popular_countries
, der Bezugs states
und countries
jeweils. Jede dieser "beliebten" Tabellen verweist auch auf das Benutzerprofil.
CREATE TABLE popular_states (
state_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(state_id, user_id),
FOREIGN KEY (state_id) REFERENCES states(state_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
CREATE TABLE popular_countries (
country_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(country_id, user_id),
FOREIGN KEY (country_id) REFERENCES countries(country_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
Dies bedeutet, dass Sie beide Tabellen abfragen müssen, um alle beliebten Lieblingsorte eines Benutzers abzurufen. Sie können sich jedoch auf die Datenbank verlassen, um die Konsistenz zu gewährleisten.
Erstellen Sie eine places
Tabelle als Supertabelle. Wie Abie erwähnt, ist eine zweite Alternative , dass Ihre Lieblingsort eine Tabelle wie Referenz places
, die ein Elternteil beide ist states
und countries
. Das heißt, sowohl Staaten als auch Länder haben auch einen Fremdschlüssel für places
(Sie können diesen Fremdschlüssel sogar als Primärschlüssel für states
und festlegen countries
).
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
PRIMARY KEY (user_id, place_id),
FOREIGN KEY (place_id) REFERENCES places(place_id)
);
CREATE TABLE states (
state_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (state_id) REFERENCES places(place_id)
);
CREATE TABLE countries (
country_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Verwenden Sie zwei Spalten. Verwenden Sie anstelle einer Spalte, die auf eine der beiden Zieltabellen verweisen kann, zwei Spalten. Diese beiden Spalten können sein NULL
; in der Tat sollte nur einer von ihnen nicht sein NULL
.
CREATE TABLE popular_areas (
place_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
state_id INT,
country_id INT,
CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
FOREIGN KEY (state_id) REFERENCES places(place_id),
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
In Bezug auf die relationale Theorie verletzen polymorphe Assoziationen die erste Normalform , da popular_place_id
es sich tatsächlich um eine Spalte mit zwei Bedeutungen handelt: Es ist entweder ein Staat oder ein Land. Sie würden die Person age
und ihre Person nicht phone_number
in einer einzigen Spalte speichern , und aus dem gleichen Grund sollten Sie nicht beide state_id
und country_id
in einer einzigen Spalte speichern . Die Tatsache, dass diese beiden Attribute kompatible Datentypen haben, ist zufällig. Sie bezeichnen immer noch verschiedene logische Entitäten.
Polymorphe Assoziationen verletzen auch die dritte Normalform , da die Bedeutung der Spalte von der zusätzlichen Spalte abhängt, die die Tabelle benennt, auf die sich der Fremdschlüssel bezieht. In der dritten Normalform darf ein Attribut in einer Tabelle nur vom Primärschlüssel dieser Tabelle abhängen.
Kommentar von @SavasVedova:
Ich bin nicht sicher, ob ich Ihrer Beschreibung folge, ohne die Tabellendefinitionen oder eine Beispielabfrage zu sehen, aber es scheint, als hätten Sie einfach mehrere Filters
Tabellen, die jeweils einen Fremdschlüssel enthalten, der auf eine zentrale Products
Tabelle verweist .
CREATE TABLE Products (
product_id INT PRIMARY KEY
);
CREATE TABLE FiltersType1 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
CREATE TABLE FiltersType2 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
...and other filter tables...
Das Verknüpfen der Produkte mit einem bestimmten Filtertyp ist einfach, wenn Sie wissen, welchem Typ Sie beitreten möchten:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Wenn der Filtertyp dynamisch sein soll, müssen Sie Anwendungscode schreiben, um die SQL-Abfrage zu erstellen. SQL erfordert, dass die Tabelle zum Zeitpunkt des Schreibens der Abfrage angegeben und festgelegt wird. Sie können die verknüpfte Tabelle nicht dynamisch basierend auf den Werten in einzelnen Zeilen von auswählen Products
.
Die einzige andere Möglichkeit besteht darin, mithilfe äußerer Verknüpfungen mit allen Filtertabellen zu verknüpfen. Diejenigen, die keine übereinstimmende product_id haben, werden nur als einzelne Zeile von Nullen zurückgegeben. Sie müssen jedoch immer noch alle verknüpften Tabellen fest codieren. Wenn Sie neue Filtertabellen hinzufügen, müssen Sie Ihren Code aktualisieren.
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Eine andere Möglichkeit, sich mit allen Filtertabellen zu verbinden, besteht darin, dies seriell zu tun:
SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
Für dieses Format müssen Sie jedoch weiterhin Verweise auf alle Tabellen schreiben. Daran führt kein Weg vorbei.
join
ändert sich auch das Ziel ...... Kompliziere ich zu viel oder was? Hilfe!