Warum können Sie in einer polymorphen Zuordnung keinen Fremdschlüssel haben, wie den unten als Rails-Modell dargestellten?
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
Option, an die weitergegeben werden kannbelongs_to
. Das OP spricht von einer "Fremdschlüsseleinschränkung" der nativen Datenbank. Das hat mich eine Weile verwirrt.Antworten:
Ein Fremdschlüssel darf nur auf eine übergeordnete Tabelle verweisen. Dies ist sowohl für die SQL-Syntax als auch für die relationale Theorie von grundlegender Bedeutung.
Eine polymorphe Zuordnung liegt vor, wenn eine bestimmte Spalte auf eine oder zwei übergeordnete Tabellen verweisen kann. Es gibt keine Möglichkeit, diese Einschränkung in SQL zu deklarieren.
Das Design für polymorphe Assoziationen verstößt gegen die Regeln für das Design relationaler Datenbanken. Ich empfehle es nicht.
Es gibt verschiedene Alternativen:
Exklusive Bögen: Erstellen Sie mehrere Fremdschlüsselspalten, die jeweils auf ein übergeordnetes Element verweisen. Erzwingen Sie, dass genau einer dieser Fremdschlüssel nicht NULL sein kann.
Beziehung umkehren: Verwenden Sie drei Viele-zu-Viele-Tabellen, die jeweils auf Kommentare und ein übergeordnetes Element verweisen.
Konkrete Supertabelle: Erstellen Sie anstelle der impliziten "kommentierbaren" Oberklasse eine echte Tabelle, auf die jede Ihrer übergeordneten Tabellen verweist. Verknüpfen Sie dann Ihre Kommentare mit dieser Supertabelle. Pseudo-Rails-Code wäre ungefähr so (ich bin kein Rails-Benutzer, behandeln Sie dies also als Richtlinie, nicht als wörtlichen Code):
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
Ich beschreibe auch polymorphe Assoziationen in meiner Präsentation Praktische objektorientierte Modelle in SQL und meinem Buch SQL Antipatterns: Vermeiden der Fallstricke der Datenbankprogrammierung .
Zu Ihrem Kommentar: Ja, ich weiß, dass es eine weitere Spalte gibt, in der der Name der Tabelle vermerkt ist, auf die der Fremdschlüssel angeblich verweist. Dieses Design wird von Fremdschlüsseln in SQL nicht unterstützt.
Was passiert zum Beispiel, wenn Sie einen Kommentar einfügen und dafür "Video" als Namen der übergeordneten Tabelle nennen
Comment
? Es existiert keine Tabelle mit dem Namen "Video". Sollte die Einfügung mit einem Fehler abgebrochen werden? Welche Einschränkung wird verletzt? Woher weiß das RDBMS, dass diese Spalte eine vorhandene Tabelle benennen soll? Wie wird mit Tabellennamen umgegangen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird?Wenn Sie die
Events
Tabelle löschen, aber Zeilen enthalten, inComments
denen Ereignisse als übergeordnetes Element angegeben sind, was sollte das Ergebnis sein? Sollte die Drop-Tabelle abgebrochen werden? Sollten ZeilenComments
verwaist sein? Sollten sie sich ändern, um auf eine andere vorhandene Tabelle zu verweisen, wie zArticles
. Haben die ID-Werte, auf die früher verwiesen wurde,Events
beim Zeigen Sinn ergebenArticles
?Diese Dilemmata sind alle auf die Tatsache zurückzuführen, dass polymorphe Assoziationen von der Verwendung von Daten (dh einem Zeichenfolgenwert) abhängen, um auf Metadaten (einen Tabellennamen) zu verweisen. Dies wird von SQL nicht unterstützt. Daten und Metadaten sind getrennt.
Definieren Sie
Commentable
als echte SQL-Tabelle, nicht nur als Adjektiv in Ihrer Rails-Modelldefinition. Es sind keine weiteren Spalten erforderlich.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
Definieren Sie die Tabellen
Articles
,Photos
undEvents
als „Unterklassen“ vonCommentable
, indem sie ihre Primärschlüssel auch ein Fremdschlüssel ReferenzierungCommentable
.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
Definieren Sie die
Comments
Tabelle mit einem Fremdschlüssel zuCommentable
.CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
Wenn Sie
Article
(zum Beispiel) eine erstellen möchten , müssen Sie auch eine neue Zeile erstellenCommentable
. So auch fürPhotos
undEvents
.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(), ... );
Wenn Sie eine erstellen möchten
Comment
, verwenden Sie einen Wert, der in vorhanden istCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
Wenn Sie Kommentare zu einem bestimmten Thema abfragen möchten
Photo
, führen Sie einige Verknüpfungen aus: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;
Wenn Sie nur die ID eines Kommentars haben und herausfinden möchten, für welche kommentierbare Ressource es sich um einen Kommentar handelt. Aus diesem Grund ist es möglicherweise hilfreich, wenn die Tabelle "Kommentierbar" angibt, auf welche Ressource sie verweist.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Anschließend müssen Sie eine zweite Abfrage ausführen, um Daten aus der jeweiligen Ressourcentabelle (Fotos, Artikel usw.) abzurufen, nachdem Sie ermittelt haben, aus
commentable_type
welcher Tabelle Sie beitreten möchten. Sie können dies nicht in derselben Abfrage tun, da SQL erfordert, dass Tabellen explizit benannt werden. Sie können keiner Tabelle beitreten, die durch Datenergebnisse in derselben Abfrage bestimmt wird.Zugegeben, einige dieser Schritte verstoßen gegen die von Rails verwendeten Konventionen. Die Rails-Konventionen sind jedoch in Bezug auf das ordnungsgemäße Design relationaler Datenbanken falsch.
quelle
Bill Karwin hat Recht, dass Fremdschlüssel nicht mit polymorphen Beziehungen verwendet werden können, da SQL nicht wirklich über ein natives Konzept polymorpher Beziehungen verfügt. Wenn Ihr Ziel, einen Fremdschlüssel zu haben, darin besteht, die referenzielle Integrität zu erzwingen, können Sie ihn über Trigger simulieren. Dies wird DB-spezifisch, aber im Folgenden sind einige kürzlich von mir erstellte Trigger aufgeführt, um das kaskadierende Löschverhalten eines Fremdschlüssels in einer polymorphen Beziehung zu simulieren:
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();
In meinem Code kann sich ein Datensatz in der
brokerages
Tabelle oder ein Datensatz in deragents
Tabelle auf einen Datensatz in dersubscribers
Tabelle beziehen .quelle