Dlaczego nie możesz mieć klucza obcego w skojarzeniu polimorficznym, takim jak ten przedstawiony poniżej jako model Rails?
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
class Event < ActiveRecord::Base
has_many :comments, :as => :commentable
end
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.Odpowiedzi:
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 wierszeComments
wskazujące zdarzenia jako ich element nadrzędny, jaki powinien być wynik? Czy opuszczanie stołu powinno zostać przerwane? Czy wiersze powinnyComments
zostać osierocone? Czy powinny się zmienić, aby odwołać się do innej istniejącej tabeli, takiej jakArticles
? Czy wartości id, które wskazywały,Events
mają jakikolwiek sens, gdy wskazują naArticles
?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.
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
iEvents
jako „podklasy” oCommentable
, dokonując ich podstawowym kluczem być również kluczowym odsyłania zagranicznychCommentable
.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
Zdefiniuj
Comments
tabelę za pomocą klucza obcego doCommentable
.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 wCommentable
. To samo dotyczyPhotos
iEvents
.INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1 INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2 INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3 INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
Jeśli chcesz utworzyć
Comment
, użyj wartości, która istnieje wCommentable
.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.
źródło
Bill Karwin ma rację, mówiąc, że klucze obce nie mogą być używane w relacjach polimorficznych, ponieważ SQL nie ma tak naprawdę natywnej koncepcji relacji polimorficznych. Ale jeśli Twoim celem posiadania klucza obcego jest wymuszenie integralności referencyjnej, możesz go zasymulować za pomocą wyzwalaczy. Jest to specyficzne dla bazy danych, ale poniżej znajduje się kilka ostatnich wyzwalaczy, które utworzyłem, aby zasymulować zachowanie usuwania kaskadowego klucza obcego w relacji polimorficznej:
CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$ BEGIN DELETE FROM subscribers WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cascade_brokerage_subscriber_delete AFTER DELETE ON brokerages FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers(); CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$ BEGIN DELETE FROM subscribers WHERE referrer_type = 'Agent' AND referrer_id = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cascade_agent_subscriber_delete AFTER DELETE ON agents FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();
W moim kodzie rekord w
brokerages
tabeli lub rekord wagents
tabeli może odnosić się do rekordu wsubscribers
tabeli.źródło