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, in Comments
denen Ereignisse als übergeordnetes Element angegeben sind, was sollte das Ergebnis sein? Sollte die Drop-Tabelle abgebrochen werden? Sollten Zeilen Comments
verwaist sein? Sollten sie sich ändern, um auf eine andere vorhandene Tabelle zu verweisen, wie z Articles
. Haben die ID-Werte, auf die früher verwiesen wurde, Events
beim Zeigen Sinn ergeben Articles
?
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.
Es fällt mir schwer, mich mit Ihrem Vorschlag "Concrete Supertable" zu beschäftigen.
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
und Events
als „Unterklassen“ von Commentable
, indem sie ihre Primärschlüssel auch ein Fremdschlüssel Referenzierung Commentable
.
CREATE TABLE Articles (
id INT PRIMARY KEY,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Definieren Sie die Comments
Tabelle mit einem Fremdschlüssel zu Commentable
.
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 erstellen Commentable
. So auch für Photos
und 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(), ... );
Wenn Sie eine erstellen möchten Comment
, verwenden Sie einen Wert, der in vorhanden ist Commentable
.
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.
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.