Klucz obcy musi odnosić się tylko do jednej tabeli nadrzędnej. Ma to fundamentalne znaczenie zarówno dla składni SQL, jak i teorii relacyjnej.
Skojarzenie polimorficzne występuje wtedy, gdy dana kolumna może odwoływać się do jednej z dwóch lub więcej tabel nadrzędnych. Nie ma możliwości zadeklarowania tego ograniczenia w SQL.
Projekt asocjacji polimorficznych łamie zasady projektowania relacyjnych baz danych. Nie polecam go używać.
Istnieje kilka alternatyw:
Ekskluzywne łuki: utwórz wiele kolumn z kluczami obcymi, z których każda odwołuje się do jednego rodzica. Wymuś, że dokładnie jeden z tych kluczy obcych może mieć wartość różną od NULL.
Odwróć relację: użyj trzech tabel wiele do wielu, z których każda odwołuje się do komentarzy i odpowiedniego elementu nadrzędnego.
Concrete Supertable: Zamiast niejawnej, „ możliwej do komentowania” nadklasy, utwórz rzeczywistą tabelę, do której odwołuje się każda z tabel nadrzędnych. Następnie połącz swoje komentarze z tym supertable. Kod pseudo-szyn wyglądałby mniej więcej tak (nie jestem użytkownikiem Railsów, więc potraktuj to jako wskazówkę, a nie kod dosłowny):
class Commentable < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commentable
end
class Article < ActiveRecord::Base
belongs_to :commentable
end
class Photo < ActiveRecord::Base
belongs_to :commentable
end
class Event < ActiveRecord::Base
belongs_to :commentable
end
Skojarzenia polimorficzne omawiam także w mojej prezentacji Praktyczne modele obiektowe w SQL oraz w mojej książce SQL Antipatterns: Avoiding the Pitfalls of Database Programming .
Odp. Twój komentarz: Tak, wiem, że istnieje inna kolumna, w której znajduje się nazwa tabeli, na którą rzekomo wskazuje klucz obcy. Ten projekt nie jest obsługiwany przez klucze obce w języku SQL.
Co się stanie, na przykład, jeśli wstawisz komentarz i nazwiesz „Wideo” jako nazwę tabeli nadrzędnej Comment
? Nie istnieje tabela o nazwie „Wideo”. Czy wstawianie powinno zostać przerwane z powodu błędu? Jakie ograniczenie jest naruszane? Skąd RDBMS wie, że ta kolumna ma nazywać istniejącą tabelę? Jak obsługuje nazwy tabel bez rozróżniania wielkości liter?
Podobnie, jeśli usuniesz Events
tabelę, ale masz w niej wiersze Comments
wskazujące zdarzenia jako ich element nadrzędny, jaki powinien być wynik? Czy opuszczanie stołu powinno zostać przerwane? Czy wiersze powinny Comments
zostać osierocone? Czy powinny się zmienić, aby odwołać się do innej istniejącej tabeli, takiej jak Articles
? Czy wartości id, które wskazywały, Events
mają jakikolwiek sens, gdy wskazują na Articles
?
Wszystkie te dylematy wynikają z faktu, że asocjacje polimorficzne polegają na używaniu danych (tj. Wartości ciągu) do odwoływania się do metadanych (nazwy tabeli). Nie jest to obsługiwane przez SQL. Dane i metadane są oddzielne.
Trudno mi się ogarnąć twoją propozycją „Concrete Supertable”.
Zdefiniuj Commentable
jako prawdziwą tabelę SQL, a nie tylko przymiotnik w definicji modelu Rails. Żadne inne kolumny nie są potrzebne.
CREATE TABLE Commentable (
id INT AUTO_INCREMENT PRIMARY KEY
) TYPE=InnoDB;
Definiowanie tabel Articles
, Photos
i Events
jako „podklasy” o Commentable
, dokonując ich podstawowym kluczem być również kluczowym odsyłania zagranicznych Commentable
.
CREATE TABLE Articles (
id INT PRIMARY KEY,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Zdefiniuj Comments
tabelę za pomocą klucza obcego do Commentable
.
CREATE TABLE Comments (
id INT PRIMARY KEY AUTO_INCREMENT,
commentable_id INT NOT NULL,
FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Jeśli chcesz utworzyć Article
(na przykład), musisz również utworzyć nowy wiersz w Commentable
. To samo dotyczy Photos
i Events
.
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
Jeśli chcesz utworzyć Comment
, użyj wartości, która istnieje w Commentable
.
INSERT INTO Comments (id, commentable_id, ...)
VALUES (DEFAULT, 2, ...);
Jeśli chcesz odpytać o komentarze danego Photo
, zrób kilka sprzężeń:
SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id)
LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id)
WHERE p.id = 2;
Gdy masz tylko identyfikator komentarza i chcesz znaleźć zasób, który można skomentować, jest to komentarz. W tym celu może się okazać, że tabela z możliwością komentowania powinna wskazać, do którego zasobu się odwołuje.
SELECT commentable_id, commentable_type FROM Commentable t
JOIN Comments c ON (t.id = c.commentable_id)
WHERE c.id = 42;
Następnie musisz uruchomić drugie zapytanie, aby pobrać dane z odpowiedniej tabeli zasobów (zdjęcia, artykuły itp.), Po znalezieniu commentable_type
tabeli, do której chcesz dołączyć. Nie możesz tego zrobić w tym samym zapytaniu, ponieważ SQL wymaga, aby tabele były nazwane jawnie; nie możesz dołączyć do tabeli określonej przez wyniki danych w tym samym zapytaniu.
Trzeba przyznać, że niektóre z tych kroków łamią konwencje używane przez Railsy. Ale konwencje Railsów są błędne w odniesieniu do prawidłowego projektowania relacyjnej bazy danych.
foreign_key
opcji, do której można przekazaćbelongs_to
. OP mówi o „Ograniczeniu klucza obcego” rodzimej bazy danych. To mnie na chwilę zdezorientowało.