To, co opisujesz, nazywa się skojarzeniami polimorficznymi. Oznacza to, że kolumna „klucz obcy” zawiera wartość identyfikatora, która musi istnieć w jednym z zestawu tabel docelowych. Zazwyczaj tabele docelowe są w jakiś sposób powiązane, na przykład są instancjami jakiejś wspólnej nadklasy danych. Potrzebna byłaby również inna kolumna obok kolumny klucza obcego, aby w każdym wierszu można było wskazać, do której tabeli docelowej się odwołuje.
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
);
Nie ma możliwości modelowania skojarzeń polimorficznych przy użyciu ograniczeń SQL. Ograniczenie klucza obcego zawsze odnosi się do jednej tabeli docelowej.
Powiązania polimorficzne są obsługiwane przez frameworki, takie jak Rails i Hibernacja. Ale wyraźnie mówią, że musisz wyłączyć ograniczenia SQL, aby korzystać z tej funkcji. Zamiast tego aplikacja lub środowisko musi wykonać równoważną pracę, aby zapewnić spełnienie odwołania. Oznacza to, że wartość klucza obcego jest obecna w jednej z możliwych tabel docelowych.
Powiązania polimorficzne są słabe pod względem wymuszania spójności bazy danych. Integralność danych zależy od wszystkich klientów uzyskujących dostęp do bazy danych z tą samą wymuszoną logiką integralności referencyjnej, a także egzekwowanie musi być wolne od błędów.
Oto kilka alternatywnych rozwiązań, które wykorzystują wymuszoną przez bazę danych integralność referencyjną:
Utwórz jedną dodatkową tabelę dla każdego celu. Na przykład popular_states
, a popular_countries
, które to odniesienie states
i countries
odpowiednio. Każda z tych „popularnych” tabel odwołuje się również do profilu użytkownika.
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),
);
Oznacza to, że aby uzyskać wszystkie ulubione ulubione miejsca użytkownika, należy wykonać zapytanie do obu tych tabel. Oznacza to jednak, że możesz polegać na bazie danych w celu wymuszenia spójności.
Utwórz places
tabelę jako supertable. Jak wspomina Abie, drugą alternatywą jest to, że twoje popularne miejsca odnoszą się do tabeli places
, która jest rodzicem zarówno dla, jak states
i dla countries
. Oznacza to, że zarówno państwa, jak i kraje mają również klucz obcy do places
(możesz nawet ustawić ten klucz obcy również jako klucz podstawowy states
i 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)
);
Użyj dwóch kolumn. Zamiast jednej kolumny, która może odwoływać się do jednej z dwóch tabel docelowych, użyj dwóch kolumn. Te dwie kolumny mogą być NULL
; w rzeczywistości tylko jeden z nich powinien być nie- 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)
);
Jeśli chodzi o teorię relacyjną, stowarzyszenia polimorficzne naruszają pierwszą normalną formę , ponieważ popular_place_id
w rzeczywistości jest to kolumna o dwóch znaczeniach: albo stan, albo kraj. Nie będzie przechowywać danej osoby age
i ich phone_number
w jednej kolumnie, i z tego samego powodu nie należy przechowywać zarówno state_id
i country_id
w jednej kolumnie. Fakt, że te dwa atrybuty mają kompatybilne typy danych, jest przypadkowy; wciąż oznaczają różne byty logiczne.
Powiązania polimorficzne również naruszają trzecią postać normalną , ponieważ znaczenie kolumny zależy od dodatkowej kolumny, która określa tabelę, do której odnosi się klucz obcy. W trzeciej postaci normalnej atrybut w tabeli musi zależeć tylko od klucza podstawowego tej tabeli.
Ponownie skomentuj @SavasVedova:
Nie jestem pewien, czy podążam za twoim opisem, nie widząc definicji tabeli lub przykładowego zapytania, ale wygląda na to, że masz po prostu wiele Filters
tabel, z których każda zawiera klucz obcy, który odwołuje się do Products
tabeli centralnej .
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...
Łączenie produktów z określonym typem filtra jest łatwe, jeśli wiesz, do którego typu chcesz dołączyć:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Jeśli chcesz, aby typ filtra był dynamiczny, musisz napisać kod aplikacji, aby zbudować zapytanie SQL. SQL wymaga, aby tabela była określona i ustalona w momencie pisania zapytania. Nie można dynamicznie wybierać połączonej tabeli na podstawie wartości znalezionych w poszczególnych wierszach Products
.
Jedyną inną opcją jest dołączenie do wszystkich tabel filtrów za pomocą sprzężeń zewnętrznych. Te, które nie mają pasującego identyfikatora_produktu, zostaną zwrócone jako pojedynczy wiersz wartości null. Ale nadal musisz na stałe zakodować wszystkie połączone tabele, a jeśli dodasz nowe tabele filtrów, musisz zaktualizować 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)
...
Innym sposobem dołączenia do wszystkich tabel filtrów jest wykonanie tego szeregowo:
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)
...
Ale ten format nadal wymaga zapisywania odniesień do wszystkich tabel. Nie można tego obejść.
join
cel również się zmieni ...... Czy komplikuję za dużo czy co? Wsparcie!