Por qué no puedes tener una clave externa de una asociación polimórfica?
Pregunta
¿Por qué no puedes tener una clave externa de una asociación polimórfica, como el que se representa a continuación como un modelo de rieles?
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
Solución
Una clave externa debe hacer referencia a una sola tabla primaria. Esto es fundamental tanto para la sintaxis SQL, y la teoría relacional.
A Asociación polimórfica es cuando una columna dada puede hacer referencia a cualquiera de dos o más tablas de padres. No hay manera de que pueda declarar que la restricción en SQL.
El asociaciones polimórficas diseño rompe las reglas de diseño de base de datos relacional. No recomiendo usarlo.
Existen varias alternativas:
-
Exclusivos Arcos: Crea varias columnas de clave externa, cada una referencia a uno de los padres. Hacer cumplir esa exactamente una de estas claves externas puede ser que no sea nula.
-
Invertir la relación:. Use tres-muchos-a-muchos cuadros, cada uno hace referencia a los comentarios y un padre respectiva
-
Hormigón supertabla: En lugar de la superclase implícita "commentable", crear una tabla real de que cada una de sus tablas padre referencias. A continuación, vincule sus comentarios a la supertabla. código de pseudo-carriles sería algo así como lo siguiente (no soy un usuario rieles, por lo que tratar esto como una guía, no código literal):
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
También cubro asociaciones polimórficas en mi presentación orientada a objetos Práctica modelos en SQL , y mi libro SQL antipatterns: se evitan los riesgos de programación de base de datos .
Re tu comentario: Sí, sé que hay otra columna que señala el nombre de la tabla que la clave externa supuestamente apunta. Este diseño no es compatible con claves externas de SQL.
Lo que ocurre, por ejemplo, si inserta un comentario y el nombre de "Video" como el nombre de la tabla padre para que Comment
? No existe una tabla llamada "Video". En caso de que el inserto se interrumpe con un error? Lo que se viola la restricción? ¿Cómo funciona el RDBMS saben que esta columna se supone que es el nombre de una tabla existente? ¿Cómo se manejan los nombres de tabla entre mayúsculas y minúsculas?
Del mismo modo, si usted eliminar la tabla Events
, pero hay filas en Comments
que indican eventos como sus padres, lo que debería ser el resultado? Debería abortarse la tabla de la gota? Deben ser huérfanos filas en Comments
? Deberían cambiar para hacer referencia a otra tabla existente, como Articles
? ¿Los valores de ID que se utiliza para apuntar a Events
tiene ningún sentido cuando se apunta a Articles
?
Estos dilemas son todos debido al hecho de que las asociaciones polimórficas depende del uso de los datos (es decir, un valor de cadena) para referirse a metadatos (un nombre de tabla). Esto no es compatible con SQL. Datos y metadatos están separados.
Estoy teniendo dificultades para envolver mi cabeza en torno a su propuesta "concreta supertabla".
-
Definir
Commentable
como una tabla de SQL real, no sólo un adjetivo en la definición del modelo de rieles. No hay otras columnas son necesarias.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
-
Definir el
Articles
mesas,Photos
yEvents
como "subclases" deCommentable
, al hacer su clave primaria sea también unCommentable
referencia de clave externa.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
-
Definir la tabla
Comments
con una clave externa aCommentable
.CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
-
Cuando se desea crear una
Article
(por ejemplo), es necesario crear una nueva fila enCommentable
también. Así también paraPhotos
yEvents
.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(), ... );
-
Cuando se desea crear un
Comment
, utilice un valor que existe enCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
-
Cuando se desea consultar los comentarios de un
Photo
dado, hacer algo de uniones: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;
-
Whes Usted tiene sólo el ID de un comentario y desea encontrar lo commentable de recursos es un comentario para. Para esto, se puede encontrar que es útil para la tabla commentable para designar qué recurso que hace referencia.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
A continuación, que había necesidad de ejecutar una segunda consulta para obtener los datos de la tabla de recursos correspondiente (fotografías, artículos, etc.), después de descubrir de qué tabla
commentable_type
para unirse a. No se puede hacerlo en la misma consulta, ya que SQL requiere que las tablas se nombran de manera explícita; no se puede unir a una mesa determinada por los resultados de datos en la misma consulta.
Es cierto que algunos de estos pasos romper las convenciones utilizadas por los rieles. Pero las convenciones carriles están mal con respecto a un diseño adecuado de base de datos relacional.
Otros consejos
Bill Karwin es correcto que las claves foráneas no se pueden utilizar con las relaciones polimórficos debido a SQL en realidad no tienen un concepto nativa relaciones polimórficas. Pero si su meta de tener una clave externa es hacer cumplir la integridad referencial se puede simular que a través de disparadores. Esto consigue específica DB, pero a continuación es algunos factores desencadenantes recientes que he creado para simular el comportamiento de eliminación en cascada una clave externa en una relación polimórfica:
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();
En mi código de un registro en la tabla brokerages
o un registro en la tabla agents
puede relacionarse con un registro en la tabla subscribers
.