为什么你不能有一个多态关联的外键?
为什么你不能有一个多态关联的外键,比如下面的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
外键只能引用一个父表。 这是SQL语法和关系理论的基础。
多态关联是给定列可以引用两个或多个父表中的任何一个。 您无法在SQL中声明该约束。
多态关联devise破坏了关系数据库devise的规则。 我不build议使用它。
有几个select:
-
独占弧:创build多个外键列,每个列引用一个父对象。 强制确保其中一个外键可以是非空的。
-
扭转关系:使用三个多对多表,每个引用评论和一个相应的父母。
-
具体Supertable:而不是隐式的“可评论的”超类,创build一个真正的表,每个父表引用。 然后把你的评论链接到那个超可靠的。 伪钢轨代码将是类似于以下内容(我不是一个Rails用户,所以视为一个指导,而不是文字代码):
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
我还介绍了我的演示中的多态关联SQL中的实用面向对象模型 ,以及我的书“ SQL反模式:避免数据库编程的陷阱” 。
回复您的评论:是的,我知道还有另一列logging了外键所指的表名。 SQL中的外键不支持此devise。
例如,如果插入注释并将“Video”命名为该Comment
的父表的名称,会发生什么情况? 没有名为“video”的表格存在。 插入是否应该中止一个错误? 什么约束被侵犯? RDBMS如何知道这个列是应该命名一个现有的表? 它如何处理不区分大小写的表名?
同样,如果删除“ Events
表,但在“ Comments
中有行表示“事件”作为其父项,那么结果应该是什么? 应该放弃drop table吗? Comments
中的行应该是孤儿吗? 他们是否应该改为参考其他现有的表格,如Articles
? 当指向“ Articles
时,用于指向“ Events
”的id值是否有意义?
这些困境都是由于多态关联依赖于使用数据(即string值)来引用元数据(表名)的事实。 这不被SQL支持。 数据和元数据是分开的。
我很难把你的“具体Supertable”提案包裹起来。
-
将
Commentable
定义为一个真正的SQL表,而不仅仅是Rails模型定义中的一个形容词。 没有其他列是必要的。CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
-
定义表
Articles
,Photos
和Events
作为Commentable
“子类”,通过使它们的主键也是引用可Commentable
的外键。CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
-
用一个外键定义
Comments
表,使之可Commentable
。CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
-
当你想创build一个
Article
(例如),你也必须在Commentable
创build一个新的行。Photos
和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(), ... );
-
当您想要创build一个
Comment
,请使用Commentable
中存在的值。INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
-
当你想查询给定的
Photo
评论,做一些连接: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;
-
当你只有一个评论的编号,你想find什么可评论的资源这是一个评论。 为此,您可能会发现可注释表指定它所引用的资源是有帮助的。
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
然后,您需要运行第二个查询来从相应的资源表(照片,文章等)中获取数据,然后从
commentable_type
发现要join的表。 你不能在同一个查询中执行它,因为SQL要求显式地命名表; 您无法join由相同查询中的数据结果确定的表格。
无可否认,其中一些步骤违反了Rails使用的约定。 但是Rails约定在正确的关系数据库devise方面是错误的。
Bill Karwin是正确的,外键不能用于多态关系,因为SQL没有真正的本原概念多态关系。 但是如果你有一个外键的目标是强制引用完整性,你可以通过触发器来模拟它。 这得到数据库的具体情况,但下面是我创build的一些最近的触发器来模拟多态关系上外键的级联删除行为:
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();
在我的代码中, brokerages
表中的logging或agents
表中的logging可以与subscribers
表中的logging相关联。