¿Por qué no puedes tener una clave foránea en una asociación polimórfica?
¿Por qué no puede tener una clave foránea en una asociación polimórfica, como la representada a continuación como un modelo de Rails?
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
2 answers
Una clave foránea solo debe hacer referencia a una tabla principal. Esto es fundamental tanto para la sintaxis SQL como para la teoría relacional.
Una Asociación polimórfica es cuando una columna dada puede hacer referencia a dos o más tablas principales. No hay manera de que pueda declarar esa restricción en SQL.
El diseño de Asociaciones polimórficas rompe las reglas del diseño de bases de datos relacionales. No recomiendo usarlo.
Hay varios alternativas:
Arcos exclusivos: Crea múltiples columnas de clave foránea, cada una haciendo referencia a un padre. Haga cumplir que exactamente una de estas claves foráneas puede ser no NULA.
Invierta la Relación: Use tres tablas de muchos a muchos, cada una hace referencia a los Comentarios y a un padre respectivo.
-
Supertable concreto: En lugar de la superclase implícita "comentable", cree una tabla real que cada uno de sus padres referencias de tablas. Luego enlaza tus comentarios a esa supertable. El código pseudo-rails sería algo como lo siguiente (no soy un usuario de Rails, así que trata esto como una guía, no como un 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 las asociaciones polimórficas en mi presentación Practical Object-Oriented Models in SQL, y mi libro SQL Antipatterns: Avoiding the Pitfalls of Database Programming.
Re su comentario: Sí, sé que hay otro columna que señala el nombre de la tabla a la que supuestamente apunta la clave foránea. Este diseño no es compatible con las claves foráneas en SQL.
¿Qué sucede, por ejemplo, si inserta un comentario y el nombre "Video" como el nombre de la tabla principal para eso Comment
? No existe una tabla llamada "Video". ¿Se debe abortar la inserción con un error? ¿Qué restricción se está violando? ¿Cómo sabe el RDBMS que se supone que esta columna debe nombrar una tabla existente? ¿Cómo se maneja sin distinción de mayúsculas y minúsculas ¿nombres de tablas?
Del mismo modo, si se elimina la tabla Events
, pero tiene filas en Comments
que indican los eventos como su padre, ¿cuál debería ser el resultado? ¿Se debe abortar la tabla de caída? ¿Deberían las filas de Comments
quedar huérfanas? ¿Deberían cambiar para referirse a otra tabla existente como Articles
? ¿Los valores id que solían apuntar a Events
tienen algún sentido cuando apuntan a Articles
?
Estos dilemas se deben al hecho de que las Asociaciones polimórficas dependen del uso de datos (es decir, string value) para referirse a metadatos (un nombre de tabla). Esto no es compatible con SQL. Los datos y los metadatos son independientes.
Estoy teniendo dificultades para envolver mi cabeza alrededor de su propuesta de "Supertable de concreto".
-
Defina
Commentable
como una tabla SQL real, no solo como un adjetivo en la definición de su modelo Rails. No se necesitan otras columnas.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
-
Definir las tablas
Articles
,Photos
, yEvents
como "subclases" deCommentable
, haciendo su clave primaria es también una clave foránea que hace referencia aCommentable
.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
-
Defina la tabla
Comments
con una clave foránea 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 desee crear un
Article
(por ejemplo), también debe crear una nueva fila enCommentable
. 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 desee crear un
Comment
, utilice un valor que exista enCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
-
Cuando desee consultar comentarios de un
Photo
dado, haga algunos une: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;
-
Cuando solo tienes el id de un comentario y quieres encontrar para qué recurso comentable es un comentario. Para esto, puede encontrar que es útil que la tabla comentable designe a qué recurso hace referencia.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Entonces tendría que ejecutar una segunda consulta para obtener datos de la tabla de recursos respectiva (Fotos, Artículos, etc.), después de descubrir desde
commentable_type
a qué tabla unirse. No puede hacerlo en la misma consulta, porque SQL requiere que las tablas sean nombradas explícitamente; no puede unirse a una tabla determinada por los resultados de datos en la misma consulta.
Es cierto que algunos de estos pasos rompen las convenciones utilizadas por Rails. Pero las convenciones de Rails están equivocadas con respecto al diseño adecuado de la base de datos relacional.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-19 18:16:51
Bill Karwin tiene razón en que las claves foráneas no se pueden usar con relaciones polimórficas debido a que SQL no tiene realmente un concepto nativo de relaciones polimórficas. Pero si su objetivo de tener una clave foránea es reforzar la integridad referencial, puede simularla a través de disparadores. Esto hace que la base de datos sea específica, pero a continuación se muestran algunos disparadores recientes que creé para simular el comportamiento de eliminación en cascada de una clave foránea 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 un registro en la tabla brokerages
o una el registro en la tabla agents
puede relacionarse con un registro en la tabla subscribers
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-08 15:22:36