Warum kann man nicht einen Fremdschlüssel in einer polymorphen Assoziation hat?
Frage
Warum kann man nicht einen Fremdschlüssel in einer polymorphen Assoziation hat, wie die unten als Rails Modell dargestellt?
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
Lösung
Ein Fremdschlüssel muss nur ein Elternteil Tabelle verweisen. Dies ist von grundlegender Bedeutung sowohl für SQL-Syntax und relationale Theorie.
Eine polymorphe Vereinigung ist, wenn eine bestimmte Spalte entweder aus zwei oder mehreren übergeordneten Tabellen verweisen kann. Es gibt keine Möglichkeit, diese Einschränkung in SQL erklären kann.
Die Polymorphe Verbände Design bricht Regeln der relationalen Datenbank-Design. Ich würde es nicht empfehlen verwenden.
Es gibt mehrere Alternativen:
-
Exclusive Arcs: Erstellen Sie mehrere Fremdschlüsselspalten, die jeweils Referenzierung ein Elternteil. Erzwingen, dass genau eine dieser Fremdschlüssel kann nicht NULL sein.
-
Kehrt die Beziehung:. Mit drei many-to-many-Tabellen, die jeweils Referenzen Kommentaren und eine entsprechende Mutter
-
Beton über- und: Anstelle der impliziten „kommentierbaren“ Superklasse, erstellen eine echte Tabelle, die jede Ihrer übergeordneten Tabellen Referenzen. Dann verknüpfen Sie Ihre Kommentare zu diesem von über-. Pseudo-Schienen Code wäre so etwas wie die folgende (ich bin kein Rails Benutzer, so behandelt dies als Richtlinie, nicht wörtlich 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 behandle auch polymorphe Verbände in meiner Präsentation Praktische Objektorientierte Models in SQL , und mein Buch SQL Antipatterns: Vermeiden von den Tücken des Datenbankprogrammierung .
Re Ihren Kommentar: Ja, ich weiß, dass es eine andere Spalte, die den Namen der Tabelle stellt fest, dass der Fremdschlüssel angeblich zu den Punkten. Dieser Entwurf wird nicht durch Fremdschlüssel in SQL unterstützt.
Was zum Beispiel passiert, wenn Sie für diese Comment
einen Kommentar und den Namen „Video“ als den Namen der übergeordneten Tabelle einfügen? Keine Tabelle mit dem Namen „Video“ existiert. Sollte der Einsatz mit einem Fehler abgebrochen werden? Welche Einschränkung verletzt wird? Wie weiß die RDBMS, dass diese Spalte soll eine vorhandene Tabelle nennen? Wie funktioniert es Groß- und Kleinschreibung Tabellennamen umgehen?
Ebenso, wenn Sie die Events
Tisch fallen, aber Sie haben Zeilen in Comments
, die Ereignisse als ihre Eltern zeigen, was soll das Ergebnis sein? Sollte die Drop-Tabelle abgebrochen werden? Sollten Zeilen in Comments
verwaist? Sollten sie wechseln in eine andere vorhandene Tabelle wie Articles
verweisen? Entsprechen die ID-Werte, die Punkt verwendet, um Events
keinen Sinn machen, wenn sie Articles
zeigen?
Diese Dilemmas alle aufgrund der Tatsache, dass polymorphe Verbände hängt Daten über die Verwendung (das heißt einen String-Wert) auf Metadaten verweisen (ein Tabellenname). Dies wird nicht von SQL unterstützt. Daten und Metadaten sind getrennt.
Ich bin um Ihre „Concrete über- und“ Vorschlag eine harte Zeit Umhüllung meinem Kopf haben.
-
definiert
Commentable
als eine echte SQL-Tabelle, nicht nur ein Adjektiv in Ihrer Rails-Modell Definition. Keine andere Spalten notwendig sind.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
-
Definieren Sie die Tabellen
Articles
,Photos
undEvents
als "Unterklassen" vonCommentable
durch ihre Primärschlüssel machen sein 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üsselCommentable
.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 eine
Article
erstellen möchten (zum Beispiel), müssen Sie eine neue Zeile inCommentable
erstellen. So ist es 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
Comment
erstellen möchten, verwenden Sie einen Wert, der inCommentable
vorhanden ist.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
-
Wenn Sie Kommentare eines bestimmten
Photo
abfragen wollen, tun einige schließt sich: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;
-
When Sie nur die ID eines Kommentars haben, und Sie wollen finden, was die Ressourcen kommentierbaren es ist ein Kommentar zu. Dazu können Sie feststellen, dass es hilfreich ist für die kommentierbaren Tabelle zu bestimmen, welche Ressourcen sie verweist.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Dann müssen Sie eine zweite Abfrage auszuführen Daten aus dem jeweiligen Ressourcentabelle zu erhalten (Fotos, Artikeln, etc.), nachdem er von
commentable_type
entdecken, die Tabelle zu verbinden. Sie können es in der gleichen Abfrage nicht tun, weil SQL verlangt, dass Tabellen explizit benannt werden; Sie können nicht zu einer Tabelle beitreten, indem Datenergebnisse in der gleichen Abfrage bestimmt.
Zugegeben, brechen einige dieser Schritte, um die von Rails Konventionen. Aber die Rails Konventionen sind falsch in Bezug auf die richtige relationalen Datenbank-Design.
Andere Tipps
Bill Karwin richtig ist, dass Fremdschlüssel kann nicht mit polymorphen Beziehungen verwendet werden, aufgrund von SQL nicht wirklich eine nativer Konzept polymorphe Beziehungen hat. Aber wenn Ihr Ziel ist ein Fremdschlüssel zu haben, ist die referentielle Integrität erzwingen können Sie es über Trigger simulieren. Dies wird DB spezifische, aber unten ist einige neue Trigger I erstellt die Kaskadierung löschen Verhalten eines Fremdschlüssels auf 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 ein Datensatz in der brokerages
Tabelle oder einen Datensatz in der Tabelle agents
auf einen Datensatz in der Tabelle subscribers
beziehen kann.