外键只能引用一个父表。这对于SQL语法和关系理论都是至关重要的。
多态关联是指给定的列可以引用两个或多个父表中的任何一个。您无法在SQL中声明该约束。
多态关联设计打破了关系数据库设计的规则。我不建议使用它。
有几种选择:
专用弧: 创建多个外键列,每个外键列均引用一个父级。强制这些外键之一可以是非NULL。
逆转关系: 使用三个多对多表,每个表都引用注释和各自的父级。
具体 的父表:创建一个父表中每个表都引用的真实表,而不是隐式的“可注释”超类。然后将您的评论链接到该超级表。伪-rails代码将类似于以下内容(我不是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反模式:避免数据库编程的陷阱》一书中,我还将介绍多态关联。
关于您的评论:是的,我确实知道还有另一列记录了外键应该指向的表的名称。SQL中的外键不支持此设计。
例如,如果您插入一个Comment并命名为“ Video”作为该父表的名称,会发生什么情况Comment
?没有名为“视频”的表。插入是否应该因错误而中止?违反了什么约束?RDBMS如何知道该列应该命名现有表?它如何处理不区分大小写的表名?
同样,如果删除Events
表,但其中有行Comments
表示“事件”为其父项,结果应该是什么?应该放弃放置表吗?是否应该Comments
孤立行?他们应该更改为引用另一个现有表Articles
吗?Events
指向时指向的id值是否有意义Articles
?
这些困境都是由于多态关联取决于使用数据(即字符串值)来引用元数据(表名)这一事实。SQL不支持此功能。数据和元数据是分开的。
对于您的“混凝土超级表”提案,我很难解决。
定义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,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
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);
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(), ... );
当您要创建时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;
当您仅具有评论的ID并且想要查找该评论的可评论资源时。为此,您可能会发现Commentable表指定其引用的资源很有帮助。
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约定在适当的关系数据库设计方面是错误的。
foreign_key
可以传递给的选项belongs_to
。OP正在谈论本机数据库的“外键约束”。那使我有些困惑。