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 Eventstabelę, ale masz w niej wiersze Commentswskazujące zdarzenia jako ich element nadrzędny, jaki powinien być wynik? Czy opuszczanie stołu powinno zostać przerwane? Czy wiersze powinny Commentszostać 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, Eventsmają 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 Commentablejako 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, Photosi Eventsjako „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 Commentstabelę 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 Photosi 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_typetabeli, 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_keyopcji, do której można przekazaćbelongs_to. OP mówi o „Ograniczeniu klucza obcego” rodzimej bazy danych. To mnie na chwilę zdezorientowało.