Ist es möglich, einen MySQL-Fremdschlüssel für eine von zwei möglichen Tabellen zu erstellen?

StackOverflow https://stackoverflow.com/questions/441001

  •  22-07-2019
  •  | 
  •  

Frage

Nun, hier ist mein Problem: Ich habe drei Tische.Regionen, Länder, Staaten.Länder können innerhalb von Regionen liegen, Staaten können innerhalb von Regionen liegen.Regionen stehen an der Spitze der Nahrungskette.

Jetzt füge ich eine Popular_areas-Tabelle mit zwei Spalten hinzu;region_id und popular_place_id.Ist es möglich, „popular_place_id“ zu einem Fremdschlüssel für eines der beiden Länder zu machen? ODER Zustände.Ich muss wahrscheinlich eine Spalte „popular_place_type“ hinzufügen, um festzustellen, ob die ID ein Land oder einen Staat beschreibt.

War es hilfreich?

Lösung

Was Sie beschreiben, nennt sich Polymorphe Assoziationen.Das heißt, die Spalte „Fremdschlüssel“ enthält einen ID-Wert, der in einer Gruppe von Zieltabellen vorhanden sein muss.Typischerweise sind die Zieltabellen in irgendeiner Weise miteinander verbunden, beispielsweise indem sie Instanzen einer gemeinsamen Oberklasse von Daten sind.Außerdem benötigen Sie neben der Fremdschlüsselspalte eine weitere Spalte, 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 eins 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 nutzen 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 hinsichtlich der Durchsetzung der Datenbankkonsistenz schwach.Die Datenintegrität hängt davon ab, dass alle Clients, die auf die Datenbank zugreifen, mit der gleichen durchgesetzten referenziellen Integritätslogik durchgesetzt werden, und außerdem muss die Durchsetzung fehlerfrei sein.

Hier sind einige alternative Lösungen, die die datenbankgestützte referenzielle Integrität nutzen:

Erstellen Sie eine zusätzliche Tabelle pro Ziel. Zum Beispiel popular_states Und popular_countries, welche Referenz states Und countries jeweils.Jede dieser „populären“ Tabellen verweist auch auf das Profil des Benutzers.

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),
);

Das bedeutet, dass Sie beide Tabellen abfragen müssen, um alle beliebten Lieblingsorte eines Benutzers zu erhalten.Aber es bedeutet, dass Sie sich darauf verlassen können, dass die Datenbank Konsistenz gewährleistet.

Ein ... kreieren places Tisch als Übertisch. Wie Abie erwähnt, besteht eine zweite Alternative darin, dass Ihre beliebten Orte auf eine Tabelle wie verweisen places, das für beide ein übergeordnetes Element ist states Und countries.Das heißt, sowohl Staaten als auch Länder haben auch einen Fremdschlüssel places (Sie können diesen Fremdschlüssel sogar zum Primärschlüssel von machen states Und 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;Tatsächlich 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 verstößt Polymorphic Associations gegen Erste Normalform, weil das popular_place_id ist eigentlich eine Spalte mit zwei Bedeutungen:es ist entweder ein Staat oder ein Land.Sie würden die einer Person nicht speichern age und ihre phone_number in einer einzigen Spalte, und aus dem gleichen Grund sollten Sie nicht beide speichern state_id Und country_id in einer einzigen Spalte.Die Tatsache, dass diese beiden Attribute kompatible Datentypen haben, ist Zufall;sie bedeuten immer noch unterschiedliche logische Einheiten.

Polymorphe Assoziationen verstoßen ebenfalls 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.


Re-Kommentar von @SavasVedova:

Ich bin mir nicht sicher, ob ich Ihrer Beschreibung folge, ohne die Tabellendefinitionen oder eine Beispielabfrage zu sehen, aber es hört sich so an, als hätten Sie einfach mehrere Filters Tabellen, die jeweils einen Fremdschlüssel enthalten, der auf einen zentralen Schlüssel verweist Products Tisch.

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 Zuordnen der Produkte zu einem bestimmten Filtertyp ist einfach, wenn Sie wissen, welchem ​​Typ Sie zuordnen 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 fixiert wird.Sie können die verknüpfte Tabelle nicht dynamisch basierend auf den Werten auswählen, die in einzelnen Zeilen von gefunden werden Products.

Die einzige andere Möglichkeit besteht darin, sich anzuschließen alle Filtern Sie Tabellen mithilfe von Outer-Joins.Diejenigen, die keine passende Produkt-ID haben, werden nur als einzelne Zeile mit Nullen zurückgegeben.Aber Sie müssen immer noch fest codieren alle die verknüpften Tabellen, und 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, alle Filtertabellen zu verknüpfen, besteht darin, dies seriell durchzuführen:

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)
...

Bei diesem Format müssen Sie jedoch weiterhin Verweise auf alle Tabellen schreiben.Daran führt kein Weg vorbei.

Andere Tipps

Dies ist nicht die eleganteste Lösung in der Welt, aber könnten Sie Betontisch Vererbung , um diese Arbeit zu machen.

Konzeptionell schlagen Sie eine Vorstellung von einer Klasse von „Dinge, die beliebtesten Regionen sein können“, aus denen Ihre drei Arten von Orten erben. Man könnte dies als eine Tabelle mit dem Namen repräsentieren, beispielsweise places wobei jede Zeile eine Beziehung Eins-zu-eins hat mit einer Reihe in regions, countries oder states. (Attribute, die zwischen den Regionen, Ländern oder Staaten, wenn überhaupt geteilt werden, gedrückt werden könnten, in diese Tabelle platziert.) Ihre popular_place_id dann ein Fremdschlüssel Bezug auf eine Zeile in den Orten Tabelle sein würde, die dann Sie zu einer Region führen würden, Land oder Staat.

Die Lösung, die Sie mit einer zweiten Säule schlagen die Art der Assoziation zu beschreiben passiert zu sein, wie Rails polymorphe Verbände behandelt, aber ich bin kein Fan davon im Allgemeinen. Bill erklärt in einem ausgezeichneten Detail, warum polymorphe Verbände sind nicht Ihre Freunde.

Hier ist eine Korrektur Bill Karwin des „über- und“ -Ansatz, eine Verbindung Schlüssel ( place_type, place_id ) mit der wahrgenommen Normalform Verletzungen zu beheben:

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);

Was ist dieser Entwurf, dass in places für jede Zeile nicht gewährleisten kann eine Zeile in states oder countries existiert (aber nicht beides). Dies ist eine Einschränkung der Fremdschlüssel in SQL. In einem vollen SQL-92-Standards konform DBMS Sie deferrable interTabellenBedingungen definieren könnten, dass Sie das gleiche erreichen erlauben würde, aber es ist klobig, beinhaltet Transaktion und so ein DBMS hat es noch auf dem Markt zu machen.

Ich weiß, dass dieser Thread ist alt, aber ich sah dies und eine Lösung in den Sinn kam, und ich dachte, dass ich es werfen würde da draußen.

Regionen, Länder und Staaten sind Geographische Lage, die in einer Hierarchie leben.

Sie können Ihr Problem ganz vermeiden, indem eine Domain Tabelle erstellt genannt geographical_location_type, die Sie mit drei Reihen (Region, Land, Staat).

bevölkern würden

Als nächstes wird anstelle der drei Standort Tabellen, erstellen Sie eine einzelne geographical_location Tabelle, die einen Fremdschlüssel von geographical_location_type_id hat (so dass Sie wissen, wenn die Instanz eine Region ist, Land oder Staat).

Modell der Hierarchie, indem sie diese Tabelle selbst verweis so dass eine staatliche Instanz die F-Taste an ihre Mutterland Instanz enthält, die wiederum die F-Taste an ihre Mutter Region Instanz enthält. Region Instanzen würden NULL in diesem FKEY halten. Das ist nicht anders als das, was Sie mit den drei Tabellen getan hätten (Sie würden 1 haben - viele Beziehungen zwischen Region und Land und zwischen Land und Staat). Außer jetzt ist alles in einer Tabelle

Die popular_user_location Tabelle ein Bereichsauflösungstabelle zwischen Benutzer und georgraphical_location sein würde (so viele Benutzer viele Orte wie könnten).

Soooo ...

CREATE TABLE [geographical_location_type] (
    [geographical_location_type_id] INTEGER NOT NULL,
    [name] VARCHAR(25) NOT NULL,
    CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)

-- Add 'Region', 'Country' and 'State' instances to the above table


CREATE TABLE [geographical_location] (
   [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
    [name] VARCHAR(1024) NOT NULL,
    [geographical_location_type_id] INTEGER NOT NULL,
    [geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
    CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)

CREATE TABLE [user] (
    [user_id] BIGINT NOT NULL,
    [login_id] VARCHAR(30) NOT NULL,
    [password] VARCHAR(512) NOT NULL,
    CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)


CREATE TABLE [popular_user_location] (
    [popular_user_location_id] BIGINT NOT NULL,
    [user_id] BIGINT NOT NULL,
    [geographical_location_id] BIGINT NOT NULL,
    CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)

ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] 
    FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])



ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] 
    FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] 
    FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] 
    FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

War mir nicht sicher, was das Ziel der DB war; die oben ist MS SQL Server.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top