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
War es hilfreich?

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 und Events als "Unterklassen" von Commentable durch ihre Primärschlüssel machen sein auch ein Fremdschlüssel Referenzierung Commentable.

    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 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 eine Article erstellen möchten (zum Beispiel), müssen Sie eine neue Zeile in Commentable erstellen. So ist es auch für Photos und Events.

    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 in Commentable 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top