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 statesi countriesodpowiednio. 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 placestabelę 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 statesi 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 statesi 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_idw rzeczywistości jest to kolumna o dwóch znaczeniach: albo stan, albo kraj. Nie będzie przechowywać danej osoby agei ich phone_numberw jednej kolumnie, i z tego samego powodu nie należy przechowywać zarówno state_idi country_idw 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 Filterstabel, z których każda zawiera klucz obcy, który odwołuje się do Productstabeli 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ść.
joincel również się zmieni ...... Czy komplikuję za dużo czy co? Wsparcie!