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!To nie jest najbardziej eleganckie rozwiązanie na świecie, ale możesz użyć dziedziczenia konkretnego stołu, aby to zadziałało.
Koncepcyjnie proponujesz pojęcie klasy „rzeczy, które mogą być popularnymi obszarami”, z których dziedziczą twoje trzy typy miejsc. Można reprezentować ten jako tabela o nazwie, na przykład,
places
gdzie każdy wiersz ma związek z rzędu jeden do jednego wregions
,countries
lubstates
. (Atrybuty, które są wspólne między regionami, krajami lub stanami, jeśli takie istnieją, mogą zostać wypchnięte do tej tabeli miejsc.) Twojepopular_place_id
odwołanie byłoby wówczas kluczem obcym do wiersza w tabeli miejsc, który następnie doprowadziłby Cię do regionu, kraju lub stan.Rozwiązaniem, które proponujesz w drugiej kolumnie opisującej rodzaj skojarzenia, jest sposób, w jaki Railsy radzą sobie z skojarzeniami polimorficznymi, ale ogólnie nie jestem fanem tego. Bill wyjaśnia szczegółowo, dlaczego skojarzenia polimorficzne nie są twoimi przyjaciółmi.
źródło
Oto poprawka do „supertable” podejścia Billa Karwina, przy użyciu złożonego klucza,
( place_type, place_id )
aby rozwiązać postrzegane naruszenia normalnej formy:Czego ten projekt nie może zapewnić, że dla każdego wiersza
places
istnieje wiersz wstates
lubcountries
(ale nie oba). Jest to ograniczenie kluczy obcych w SQL. W systemie DBMS w pełni zgodnym ze standardami SQL-92 można zdefiniować odroczone ograniczenia między tabelami, które pozwoliłyby na osiągnięcie tego samego, ale jest niezgrabne, wymaga transakcji i taki system DBMS musi jeszcze wejść na rynek.źródło
Zdaję sobie sprawę, że ten wątek jest stary, ale zobaczyłem to i przyszło mi do głowy rozwiązanie i myślałem, że go tam wyrzucę.
Regiony, kraje i stany to lokalizacje geograficzne, które żyją w hierarchii.
Możesz całkowicie uniknąć tego problemu, tworząc tabelę domen o nazwie typ_lokalizacji geograficznej, którą zapełnisz trzema wierszami (Region, Kraj, Stan).
Następnie zamiast trzech tabel lokalizacji utwórz pojedynczą tabelę lokalizacji geograficznej, która ma klucz obcy identyfikator_typu lokalizacji geograficznej (dzięki czemu wiesz, czy instancja to Region, Kraj lub Stan).
Modeluj hierarchię, czyniąc tę tabelę samoreferencyjną, tak aby instancja stanu przechowywała klucz fKey do swojej instancji nadrzędnej Country, która z kolei przechowuje fKey do instancji nadrzędnej regionu. Instancje regionu miałyby wartość NULL w tym kluczu. Nie różni się to od tego, co zrobiłbyś z trzema tabelami (miałbyś 1 - wiele relacji między regionem a krajem oraz między krajem a stanem), z wyjątkiem tego, że wszystko jest w jednej tabeli.
Popularna tabela_użytkownika byłaby tabelą rozdzielczości zakresu między użytkownikiem a georgraphical_location (tak wielu użytkowników może polubić wiele miejsc).
Soooo…
Nie byłem pewien, jaki był docelowy DB; powyższe to MS SQL Server.
źródło
Mam dwie tabele:
a) Numer piosenki b) Tytuł piosenki ....
i mam trzeci
Problem polega na tym, że niektóre rodzaje list odtwarzania mają link do innych list odtwarzania. Ale w mysql nie mamy klucza obcego powiązanego z dwiema tabelami.
Moje rozwiązanie: wstawię trzecią kolumnę w pliku song_to_playlist_relation. Ta kolumna będzie logiczna. Jeśli 1, to piosenka, w przeciwnym razie będzie link do tabeli list odtwarzania.
Więc:
a) Numer_listy (int) b) Czy piosenka (boolean) c) Numer względny (numer piosenki lub numer listy odtwarzania) (int) ( nie klucz obcy do żadnej tabeli)
To wszystko!źródło