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!Dies ist nicht die eleganteste Lösung der Welt, aber Sie können die konkrete Tabellenvererbung verwenden , damit dies funktioniert.
Konzeptionell schlagen Sie eine Vorstellung von einer Klasse von "Dingen vor, die beliebte Gebiete sein können", von denen Ihre drei Arten von Orten erben. Man könnte dies als eine Tabelle mit dem Namen repräsentieren, zum Beispiel,
places
in der jede Zeile eine Beziehung mit einer Reihe Eins-zu-eins hat inregions
,countries
oderstates
. (Attribute, die zwischen Regionen, Ländern oder Staaten geteilt werden, falls vorhanden, könnten in diese Orts-Tabelle verschoben werden.) Siepopular_place_id
wären dann ein Fremdschlüsselverweis auf eine Zeile in der Orts-Tabelle, die Sie dann zu einer Region, einem Land führen würde oder Zustand.Die Lösung, die Sie mit einer zweiten Spalte vorschlagen, um die Art der Assoziation zu beschreiben, ist, wie Rails mit polymorphen Assoziationen umgeht, aber ich bin im Allgemeinen kein Fan davon. Bill erklärt ausführlich, warum polymorphe Assoziationen nicht deine Freunde sind.
quelle
Hier ist eine Korrektur von Bill Karwins "Supertable" -Ansatz unter Verwendung eines zusammengesetzten Schlüssels
( place_type, place_id )
, um die wahrgenommenen Verstöße gegen die normale Form zu beheben:Was dieses Design nicht sicherstellen kann, dass für jede Zeile
places
eine Zeile instates
odercountries
(aber nicht beide) vorhanden ist. Dies ist eine Einschränkung von Fremdschlüsseln in SQL. In einem vollständigen SQLMS-konformen DBMS könnten Sie aufschiebbare Einschränkungen zwischen Tabellen definieren, die es Ihnen ermöglichen würden, dasselbe zu erreichen, aber es ist klobig, beinhaltet Transaktionen und ein solches DBMS muss es noch auf den Markt bringen.quelle
Mir ist klar, dass dieser Thread alt ist, aber ich habe das gesehen und mir ist eine Lösung in den Sinn gekommen, und ich dachte, ich würde ihn da rauswerfen.
Regionen, Länder und Staaten sind geografische Standorte, die in einer Hierarchie leben.
Sie können Ihr Problem vollständig vermeiden, indem Sie eine Domänentabelle mit dem Namen geographical_location_type erstellen, die Sie mit drei Zeilen (Region, Land, Bundesland) füllen würden.
Erstellen Sie als Nächstes anstelle der drei Standorttabellen eine einzelne geografische Standorttabelle mit dem Fremdschlüssel geographical_location_type_id (damit Sie wissen, ob es sich bei der Instanz um eine Region, ein Land oder ein Bundesland handelt).
Modellieren Sie die Hierarchie, indem Sie diese Tabelle selbstreferenzieren, sodass eine State-Instanz den fKey für ihre übergeordnete Country-Instanz enthält, die wiederum den fKey für ihre übergeordnete Region-Instanz enthält. Regionsinstanzen würden in diesem fKey NULL enthalten. Dies unterscheidet sich nicht von dem, was Sie mit den drei Tabellen gemacht hätten (Sie hätten 1 - viele Beziehungen zwischen Region und Land sowie zwischen Land und Staat), außer jetzt ist alles in einer Tabelle.
Die Tabelle popular_user_location wäre eine Tabelle zur Bereichsauflösung zwischen user und georgraphical_location (so viele Benutzer könnten viele Orte mögen).
Soooo…
War nicht sicher, was die Ziel-DB war; Das obige ist MS SQL Server.
quelle
Nun, ich habe zwei Tabellen:
a) Songnummer b) Songtitel ....
und ich habe einen dritten
Das Problem ist, dass einige Arten von Wiedergabelisten Links zu anderen Wiedergabelisten haben. In MySQL haben wir jedoch keinen Fremdschlüssel, der zwei Tabellen zugeordnet ist.
Meine Lösung: Ich werde eine dritte Spalte in songs_to_playlist_relation einfügen. Diese Spalte ist boolesch. Wenn 1 dann Lied, wird sonst mit der Wiedergabelistentabelle verlinkt.
So:
a) Playlist_number (int) b) Ist Song (boolean) c) Relative Nummer (Song-Nummer oder Playlist-Nummer) (int) ( kein Fremdschlüssel für eine Tabelle)
Das ist alles!quelle