sql >> Databasteknik >  >> RDS >> Mysql

Möjligt att göra en MySQL främmande nyckel till en av två möjliga tabeller?

Det du beskriver kallas polymorfa associationer. Det vill säga, kolumnen "främmande nyckel" innehåller ett id-värde som måste finnas i en av en uppsättning måltabeller. Vanligtvis är måltabellerna relaterade på något sätt, som att de är instanser av någon vanlig superklass av data. Du skulle också behöva en annan kolumn vid sidan av kolumnen för främmande nyckel, så att du på varje rad kan ange vilken måltabell som refereras till.

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

Det finns inget sätt att modellera polymorfa associationer med SQL-begränsningar. En främmande nyckel-begränsning refererar alltid till en måltabell.

Polymorfa föreningar stöds av ramverk som Rails och Hibernate. Men de säger uttryckligen att du måste inaktivera SQL-begränsningar för att använda den här funktionen. Istället måste ansökan eller ramverket utföra motsvarande arbete för att säkerställa att referensen är uppfylld. Det vill säga, värdet i den främmande nyckeln finns i en av de möjliga måltabellerna.

Polymorfa associationer är svaga när det gäller att upprätthålla databaskonsistens. Dataintegriteten beror på att alla klienter får åtkomst till databasen med samma referensintegritetslogik påtvingad, och tillämpningen måste dessutom vara buggfri.

Här är några alternativa lösningar som drar fördel av databaspåtvingad referensintegritet:

Skapa en extra tabell per mål. Till exempel popular_states och popular_countries , vilken referens states och countries respektive. Var och en av dessa "populära" tabeller refererar också till användarens 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),
);

Detta betyder att för att få alla en användares populära favoritplatser måste du fråga båda dessa tabeller. Men det betyder att du kan lita på databasen för att upprätthålla konsekvens.

Skapa en places bord som ett superbord. Som Abie nämner är ett andra alternativ att dina populära platser refererar till en tabell som places , som är en förälder till båda states och countries . Det vill säga, både stater och länder har också en främmande nyckel till places (du kan till och med göra att denna främmande nyckel också är primärnyckeln för states och 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)
);

Använd två kolumner. Använd två kolumner istället för en kolumn som kan referera till någon av två måltabeller. Dessa två kolumner kan vara NULL; i själva verket borde bara en av dem vara icke-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)
);

När det gäller relationsteori bryter polymorfa associationer mot första normalformen , eftersom popular_place_id är i själva verket en kolumn med två betydelser:det är antingen en stat eller ett land. Du skulle inte lagra en persons age och deras phone_number i en enda kolumn, och av samma anledning bör du inte lagra båda state_id och country_id i en enda kolumn. Det faktum att dessa två attribut har kompatibla datatyper är en slump; de betecknar fortfarande olika logiska enheter.

Polymorfa föreningar bryter också mot tredje normalformen , eftersom betydelsen av kolumnen beror på den extra kolumnen som namnger tabellen som den främmande nyckeln refererar till. I tredje normalform måste ett attribut i en tabell endast bero på den primära nyckeln i den tabellen.

Om kommentar från @SavasVedova:

Jag är inte säker på att jag följer din beskrivning utan att se tabelldefinitionerna eller en exempelfråga, men det låter som att du helt enkelt har flera Filters tabeller, som var och en innehåller en främmande nyckel som refererar till en central Products tabell.

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

Att sammanfoga produkterna till en specifik typ av filter är enkelt om du vet vilken typ du vill ansluta till:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

Om du vill att filtertypen ska vara dynamisk måste du skriva applikationskod för att konstruera SQL-frågan. SQL kräver att tabellen specificeras och fixeras när du skriver frågan. Du kan inte få den sammanfogade tabellen att väljas dynamiskt baserat på värdena som finns i enskilda rader av Products .

Det enda andra alternativet är att gå med i alla filtrera tabeller med hjälp av yttre skarvar. De som inte har något matchande product_id kommer bara att returneras som en enda rad med null. Men du måste fortfarande hårdkoda alla de sammanfogade tabellerna, och om du lägger till nya filtertabeller måste du uppdatera din kod.

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

Ett annat sätt att gå med i alla filtertabeller är att göra det i serie:

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 detta format kräver fortfarande att du skriver referenser till alla tabeller. Det går inte att komma runt det.



  1. Android- Måste jag kontrollera om tabellen finns i SqliteHelper.onCreate()?

  2. 5 Fördelar med proaktiv övervakning av databasprestanda

  3. Välja en processor för SQL Server 2014 – Del 1

  4. SQLite AVG