Perché non si dispone di una chiave esterna in una polimorfici associazione?
Domanda
Perché non si dispone di una chiave esterna in una polimorfici associazione, come rappresentato di seguito come Rotaie modello?
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
Soluzione
Una chiave esterna deve fare riferimento solo a una tabella padre.Questo è fondamentale sia per la sintassi SQL e teoria relazionale.
Polimorfi Associazione è quando una colonna può fare riferimento a uno dei due o più tabelle padre.Non c'è modo si può dichiarare che il vincolo in SQL.
Il Polimorfico Associazioni design rompe le regole di progettazione di un database relazionale.Io non consiglio di utilizzarlo.
Ci sono diverse alternative:
Esclusiva Archi: Creare più colonne di chiave esterna, ciascuno dei quali fa riferimento a un genitore.Applicare esattamente una di queste chiavi esterne possono essere non NULLO.
Invertire il Rapporto: Utilizzare tre molti-a-molti tavoli, ogni riferimenti Commenti e i rispettivi genitori.
Calcestruzzo Supertable: Invece l'implicita "commentable" superclasse, creare una vera e propria tabella che ciascun genitore tabelle di riferimento.Quindi collegare i Commenti che supertable.Pseudo-guide di codice dovrebbe essere qualcosa di simile (io non sono un Guide utente, in modo da trattare questo come una linea guida, non letterale), del codice):
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
Anche io copertura polimorfici associazioni nella mia presentazione Pratico Object-Oriented Modelli in SQL, e il mio libro SQL Antipatterns:Evitando le Trappole della Programmazione di Database.
Re il tuo commento:Sì, so che c'è un'altra colonna note con il nome della tabella che la chiave esterna presumibilmente punti.Questo disegno non è supportato da chiavi esterne in SQL.
Cosa succede, per esempio, se inserisci un Commento nome e "Video" come il nome del padre tabella Comment
?Nessuna tabella denominata "Video" esiste.Dovrebbe inserto essere interrotta con un errore?Cosa vincolo è violato?Come funziona il RDBMS sapere che questa colonna si suppone che il nome di una tabella esistente?Come fa a gestire e minuscole, i nomi di tabella?
Allo stesso modo, se si rilascia l' Events
tabella, ma è necessario che le righe in Comments
che indicano gli Eventi come il loro padre, quello che dovrebbe essere il risultato?Dovrebbe drop table essere interrotta?Dovrebbe righe Comments
essere isolati?Dovrebbero cambiare per fare riferimento a un'altra tabella esistente come Articles
?Do i valori di id che consente di scegliere Events
alcun senso quando si punta a Articles
?
Questi dilemmi sono tutti dovuti al fatto che Polimorfici Associazioni dipende dall'utilizzo dati (es.un valore di stringa) per fare riferimento ai metadati (nome di una tabella).Questo non è supportato da SQL.I dati e i metadati sono separati.
Sto avendo un momento difficile avvolgere la mia testa intorno al vostro "Concrete Supertable" la proposta di.
Definire
Commentable
come un vero tabella SQL, non solo aggettivo i Rails definizione del modello.No le altre colonne sono necessari.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
Definire le tabelle
Articles
,Photos
, eEvents
come "sottoclassi" diCommentable
, rendendo le proprie chiave primaria essere anche una chiave esterna che fa riferimentoCommentable
.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
Definire il
Comments
tabella con una chiave esternaCommentable
.CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
Quando si desidera creare un
Article
(per esempio), si deve creare una nuova riga nellaCommentable
troppo.Così anche perPhotos
eEvents
.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(), ... );
Quando si desidera creare un
Comment
, utilizzare un valore che esiste inCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
Quando si desidera eseguire la query commenti di un dato
Photo
, fare qualche join: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;
Quando si hanno solo l'id di un commento e si desidera trovare quello che commentable risorsa è un commento.Per questo, si potrebbe scoprire che è utile per il Commentable tabella di designare quale risorsa di riferimento.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Quindi è necessario eseguire una seconda query per ottenere i dati dalla rispettiva tabella risorse (Foto, Articoli, etc.), dopo la scoperta da
commentable_type
la tabella a cui unirti.Non è possibile farlo nella stessa query, perché SQL richiede che le tabelle di essere nominato esplicitamente;non è possibile aderire a una tabella determinato dai risultati dei dati nella stessa query.
Certo, alcuni di questi passaggi rompere le convenzioni utilizzate dai Binari.Ma le Guide le convenzioni sono sbagliato con il rispetto per la corretta progettazione di database relazionali.
Altri suggerimenti
Bill Karwin è corretto che le chiavi esterne non possono essere utilizzati con i rapporti polimorfi a causa di SQL in realtà non avendo un concetto nativo relazioni polimorfiche. Ma se il vostro obiettivo di avere una chiave esterna è quella di applicare l'integrità referenziale è possibile simulare via trigger. Questo diventa DB specifico, ma sotto è alcuni trigger recenti che ho creato per simulare il comportamento a cascata cancellare di una chiave esterna in una relazione polimorfica:
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();
Nel mio codice di un record nella tabella brokerages
o un record nella tabella agents
può riferirsi a un record nella tabella subscribers
.