Una clave externa debe hacer referencia solo 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 determinada puede hacer referencia a dos o más tablas principales. No hay forma 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.
Existen varias alternativas:
Arcos exclusivos: cree varias columnas de clave externa, cada una de las cuales hace referencia a un padre. Haga cumplir que exactamente una de estas claves externas puede ser no NULL.
Invertir la relación: utilice tres tablas de varios a varios, cada una de las cuales hace referencia a los comentarios y al padre respectivo.
Supertable concreto: en lugar de la superclase "comentable" implícita, cree una tabla real a la que haga referencia cada una de sus tablas principales. Luego, vincule sus Comentarios a esa supertabla. El código de pseudo-rails sería algo como lo siguiente (no soy un usuario de Rails, así que trátelo 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 en mi libro SQL Antipatterns: Avoiding the Pitfalls of Database Programming .
Re su comentario: Sí, sé que hay otra columna que anota el nombre de la tabla a la que supuestamente apunta la clave externa. Este diseño no es compatible con claves externas en SQL.
¿Qué sucede, por ejemplo, si inserta un comentario y nombra "Video" como el nombre de la tabla principal para eso Comment
? No existe ninguna tabla llamada "Video". ¿Debe abortarse la inserción con un error? ¿Qué restricción se está infringiendo? ¿Cómo sabe el RDBMS que se supone que esta columna nombra una tabla existente? ¿Cómo maneja los nombres de tablas que no distinguen entre mayúsculas y minúsculas?
Del mismo modo, si quita la Events
tabla, pero tiene filas Comments
que indican Eventos como su padre, ¿cuál debería ser el resultado? ¿Debe abortarse la tabla de caída? ¿Deberían Comments
quedar huérfanas las filas en ? ¿Deberían cambiar para hacer referencia a otra tabla existente como Articles
? ¿Los valores de id que solían señalar Events
tienen algún sentido al señalar Articles
?
Todos estos dilemas se deben al hecho de que las asociaciones polimórficas dependen del uso de datos (es decir, un valor de cadena) para hacer referencia a los metadatos (un nombre de tabla). Esto no es compatible con SQL. Los datos y los metadatos están separados.
Me está costando entender tu propuesta de "Concrete Supertable".
Defínalo 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;
Defina las tablas Articles
, Photos
y Events
como "subclases" de Commentable
, haciendo que su clave principal sea también una referencia de clave externa Commentable
.
CREATE TABLE Articles (
id INT PRIMARY KEY,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Defina la Comments
tabla con una clave externa a Commentable
.
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 en Commentable
. También para Photos
y 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(), ... );
Cuando desee crear un Comment
, use un valor que exista en Commentable
.
INSERT INTO Comments (id, commentable_id, ...)
VALUES (DEFAULT, 2, ...);
Cuando desee consultar los comentarios de un determinado Photo
, realice algunas combinaciones:
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 tiene la identificación de un comentario y desea encontrar para qué recurso comentable es un comentario. Para esto, puede resultarle útil que la tabla de comentarios 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;
Luego, deberá ejecutar una segunda consulta para obtener datos de la tabla de recursos respectiva (fotos, artículos, etc.), después de descubrir desde commentable_type
qué tabla unirse. No puede hacerlo en la misma consulta, porque SQL requiere que las tablas se nombren explícitamente; no puede unirse a una tabla determinada por los resultados de los datos en la misma consulta.
Es cierto que algunos de estos pasos rompen las convenciones utilizadas por Rails. Pero las convenciones de Rails son incorrectas con respecto al diseño adecuado de bases de datos relacionales.
foreign_key
opción a la que se puede pasarbelongs_to
. El OP está hablando de una "restricción de clave externa" de la base de datos nativa. Eso me confundió por un tiempo.