sql >> Database teknologi >  >> RDS >> Mysql

Muligt at lave en MySQL fremmednøgle til en af ​​to mulige tabeller?

Det du beskriver kaldes polymorfe associationer. Det vil sige, at kolonnen "fremmed nøgle" indeholder en id-værdi, der skal eksistere i en af ​​et sæt måltabeller. Typisk er måltabellerne relaterede på en eller anden måde, såsom at være forekomster af en almindelig superklasse af data. Du skal også bruge en anden kolonne ved siden af ​​kolonnen med fremmednøgle, så du på hver række kan angive, hvilken måltabel der refereres til.

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

Der er ingen måde at modellere polymorfe associationer ved hjælp af SQL-begrænsninger. En fremmednøgle-begrænsning refererer altid til én måltabel.

Polymorfe associationer understøttes af rammer som Rails og Hibernate. Men de siger eksplicit, at du skal deaktivere SQL-begrænsninger for at bruge denne funktion. I stedet skal ansøgningen eller rammen udføre tilsvarende arbejde for at sikre, at referencen er opfyldt. Det vil sige, at værdien i fremmednøglen er til stede i en af ​​de mulige måltabeller.

Polymorfe associationer er svage med hensyn til at håndhæve databasekonsistens. Dataintegriteten afhænger af, at alle klienter får adgang til databasen med den samme referenceintegritetslogik håndhævet, og håndhævelsen skal også være fejlfri.

Her er nogle alternative løsninger, der udnytter databasepåtvunget referenceintegritet:

Opret én ekstra tabel pr. mål. For eksempel popular_states og popular_countries , som reference states og countries henholdsvis. Hver af disse "populære" tabeller refererer også til brugerens profil.

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

Dette betyder, at for at få alle en brugers populære favoritsteder, skal du forespørge begge disse tabeller. Men det betyder, at du kan stole på, at databasen håndhæver konsistens.

Opret en places bord som et superbord. Som Abie nævner, er et andet alternativ, at dine populære steder refererer til en tabel som places , som er en forælder til begge states og countries . Det vil sige, at både stater og lande også har en fremmednøgle til places (du kan endda gøre denne fremmednøgle også den primære nøgle for states og 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)
);

Brug to kolonner. I stedet for én kolonne, der kan referere til en af ​​to måltabeller, skal du bruge to kolonner. Disse to kolonner kan være NULL; faktisk skulle kun én af dem være ikke-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)
);

Med hensyn til relationsteori krænker polymorfe associationer First Normal Form , fordi popular_place_id er faktisk en kolonne med to betydninger:det er enten en stat eller et land. Du ville ikke gemme en persons age og deres phone_number i en enkelt kolonne, og af samme grund bør du ikke gemme begge state_id og country_id i en enkelt kolonne. Det faktum, at disse to attributter har kompatible datatyper, er tilfældigt; de betegner stadig forskellige logiske enheder.

Polymorfe associationer krænker også Tredje normalform , fordi betydningen af ​​kolonnen afhænger af den ekstra kolonne, som navngiver den tabel, som fremmednøglen refererer til. I tredje normalform må en attribut i en tabel kun afhænge af den primære nøgle i denne tabel.

Rekommentar fra @SavasVedova:

Jeg er ikke sikker på, at jeg følger din beskrivelse uden at se tabeldefinitionerne eller en eksempelforespørgsel, men det lyder som om, du simpelthen har flere Filters tabeller, der hver indeholder en fremmednøgle, der refererer til en central Products bord.

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

Det er nemt at forbinde produkterne med en bestemt type filter, hvis du ved, hvilken type du vil tilslutte dig:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

Hvis du ønsker, at filtertypen skal være dynamisk, skal du skrive applikationskode for at konstruere SQL-forespørgslen. SQL kræver, at tabellen er specificeret og rettet på det tidspunkt, du skriver forespørgslen. Du kan ikke få den sammenføjede tabel til at blive valgt dynamisk baseret på de værdier, der findes i individuelle rækker af Products .

Den eneste anden mulighed er at deltage i alle filtrer tabeller ved hjælp af ydre sammenføjninger. De, der ikke har noget matchende product_id, vil bare blive returneret som en enkelt række med nuller. Men du skal stadig hardkode alle de sammenføjede tabeller, og hvis du tilføjer nye filtertabeller, skal du opdatere din kode.

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

En anden måde at slutte sig til alle filtertabeller på er at gøre det serielt:

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

Men dette format kræver stadig, at du skriver referencer til alle tabeller. Det er der ingen udenom.



  1. Komplet vejledning til at rette SQL-databasefejl 5243

  2. MariaDB JSON_ARRAYAGG() Forklaret

  3. Sådan fanges og analyseres SQL Server-hændelser

  4. Hvordan tilføjer man kolonne, hvis den ikke findes på PostgreSQL?