为什么在多态关联中不能有外键,例如下面表示为 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 中声明该约束。
多态关联设计打破了关系数据库设计的规则。我不建议使用它。
有几种选择:
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
我还在演讲 Practical Object-Oriented Models in SQL 中介绍了多态关联和我的书 SQL Antipatterns: Avoiding the Pitfalls of Database Programming .
回复您的评论:是的,我知道还有另一列记录了外键应该指向的表的名称。 SQL 中的外键不支持这种设计。
例如,如果您插入评论并将“视频”命名为该
Comment
的父表的名称,会发生什么情况? ?不存在名为“视频”的表。插入是否应该因错误而中止?违反了什么约束? RDBMS 如何知道该列应该命名现有表?它如何处理不区分大小写的表名?同样,如果您删除
Events
表,但您在 Comments
中有行表明 Events 作为它们的父级,结果应该是什么?应该中止删除表吗?应该在 Comments
行成为孤儿?它们是否应该更改为引用另一个现有表,例如 Articles
?做以前指向Events
的id值指向 Articles
时有意义?这些困境都是由于多态关联依赖于使用数据(即字符串值)来引用元数据(表名)这一事实。这不受 SQL 支持。数据和元数据是分开的。
I'm having a hard time wrapping my head around your "Concrete Supertable" proposal.
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;
Article
(例如),您必须在 Commentable
中创建一个新行也是。 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(), ... );
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;
SELECT commentable_id, commentable_type FROM Commentable t
JOIN Comments c ON (t.id = c.commentable_id)
WHERE c.id = 42;
然后,在从
commentable_type
发现之后,您需要运行第二个查询以从相应的资源表(照片、文章等)中获取数据。要加入哪个表。您不能在同一个查询中执行此操作,因为 SQL 要求显式命名表;您不能加入由同一查询中的数据结果确定的表。 诚然,其中一些步骤违反了 Rails 使用的约定。但是 Rails 约定在正确的关系数据库设计方面是错误的。
关于ruby-on-rails - 为什么在多态关联中不能有外键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/922184/