egg*_*rop 75 database ruby-on-rails foreign-key-relationship polymorphic-associations
为什么在多态关联中没有外键,例如下面表示为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
Run Code Online (Sandbox Code Playgroud)
Bil*_*win 167
外键必须仅引用一个父表.这是SQL语法和关系理论的基础.
多态关联是指给定列可以引用两个或更多个父表中的任何一个.你无法在SQL中声明该约束.
多态关联设计打破了关系数据库设计的规则.我不建议使用它.
有几种选择:
独占弧: 创建多个外键列,每个列引用一个父键.强制确切地说,其中一个外键可以是非NULL.
反转关系: 使用三个多对多表,每个表引用注释和相应的父表.
Concrete Supertable: 创建一个每个父表引用的实际表,而不是隐式的"可注释"超类.然后将您的评论链接到该超级表格.伪轨代码将类似于以下内容(我不是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
Run Code Online (Sandbox Code Playgroud)我还在我的演示文稿中介绍了SQL中的实用面向对象模型,以及我的SQL Antipatterns:避免数据库编程的陷阱.
重新评论:是的,我知道还有另一列记录了外键所指的表的名称.SQL中的外键不支持此设计.
例如,如果您插入注释并将"Video"命名为父表的名称,会发生Comment
什么?没有名为"Video"的表格.插入是否应该中止错误?违反了什么约束?RDBMS如何知道该列应该命名现有的表?它如何处理不区分大小写的表名?
同样,如果你删除Events
表,但是你有行Comments
表明事件是他们的父,那么结果应该是什么?丢弃表应该中止吗?行Comments
应该成为孤儿吗?他们应该改变以引用另一个现有的表格,例如Articles
?Events
当指向时,过去指向的id值是否有意义Articles
?
这些困境都归因于多态关联依赖于使用数据(即字符串值)来引用元数据(表名).SQL不支持此功能.数据和元数据是分开的.
我很难绕过你的"具体的Supertable"提案.
定义Commentable
为真正的SQL表,而不仅仅是Rails模型定义中的形容词.不需要其他列.
CREATE TABLE Commentable (
id INT AUTO_INCREMENT PRIMARY KEY
) TYPE=InnoDB;
Run Code Online (Sandbox Code Playgroud)通过使其主键也是外键引用来定义表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.
Run Code Online (Sandbox Code Playgroud)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;
Run Code Online (Sandbox Code Playgroud)如果要创建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(), ... );
Run Code Online (Sandbox Code Playgroud)如果要创建a Comment
,请使用存在的值Commentable
.
INSERT INTO Comments (id, commentable_id, ...)
VALUES (DEFAULT, 2, ...);
Run Code Online (Sandbox Code Playgroud)如果要查询给定的注释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;
Run Code Online (Sandbox Code Playgroud)如果您只有评论的ID,并且想要找到哪个可评论的资源,那么它就是评论.为此,您可能会发现Commentable表有助于指定它引用的资源.
SELECT commentable_id, commentable_type FROM Commentable t
JOIN Comments c ON (t.id = c.commentable_id)
WHERE c.id = 42;
Run Code Online (Sandbox Code Playgroud)
然后,在发现要从commentable_type
哪个表加入之后,您需要运行第二个查询以从相应的资源表(照片,文章等)中获取数据.您无法在同一查询中执行此操作,因为SQL要求显式命名表; 您无法加入由同一查询中的数据结果确定的表.
不可否认,其中一些步骤违反了Rails使用的约定.但是Rails约定在适当的关系数据库设计方面是错误的.
归档时间: |
|
查看次数: |
28689 次 |
最近记录: |