为什么多态关联中不能有外键?
为什么在多态关联中不能有外键,例如下面作为 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
Why can you not have a foreign key in a polymorphic association, such as the one represented below as a Rails model?
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
外键必须仅引用一个父表。 这是 SQL 语法和关系理论的基础。
多态关联是指给定列可以引用两个或多个父表中的一个。 您无法在 SQL 中声明该约束。
多态关联设计打破了关系数据库设计的规则。 我不建议使用它。
有几种替代方案:
独占弧:创建多个外键列,每个外键列引用一个父级。 强制要求这些外键之一可以为非 NULL。
反转关系:使用三个多对多表,每个表都引用 Comments 和各自的父表。
具体超级表:创建每个父表引用的真实表,而不是隐式的“可注释”超类。 然后将您的评论链接到该超级表。 伪 Rails 代码将类似于以下内容(我不是 Rails 用户,因此请将其视为指南,而不是文字代码):
我还在演示文稿中介绍了多态关联Slideshare.net/billkarwin/practical-object-driven-models-in-sql" rel="noreferrer">SQL 中的实用面向对象模型,以及我的书 SQL 反模式卷 1:避免数据库编程的陷阱。
回复您的评论:是的,我确实知道还有另一列记录了外键应该指向的表的名称。 SQL 中的外键不支持这种设计。
例如,如果您插入一条评论并将“视频”命名为该评论的父表名称,会发生什么情况? 不存在名为“Video”的表。 插入是否应该因错误而中止? 违反了什么约束? RDBMS 如何知道该列应该命名现有表? 它如何处理不区分大小写的表名?
同样,如果您删除
Events
表,但Comments
中有行指示 Events 作为其父项,结果应该是什么? 是否应该中止删除表?Comments
中的行应该被孤立吗? 它们是否应该更改为引用另一个现有表,例如Articles
? 用于指向Events
的 id 值在指向Articles
时是否有意义?这些困境都是由于多态关联依赖于使用数据(即字符串值)来引用元数据(表名称)这一事实造成的。 SQL 不支持此功能。 数据和元数据是分开的。
将
Commentable
定义为真正的 SQL 表,而不仅仅是 Rails 模型定义中的形容词。 不需要其他列。将表
Articles
、Photos
和Events
定义为Commentable
的“子类”,方法是使它们主键也可以是引用Commentable
的外键。使用
Commentable
的外键定义Comments
表。例如,当您想要创建
Article
时,您也必须在Commentable
中创建一个新行。照片
和活动
也是如此。当您想要创建
Comment
时,请使用Commentable
中存在的值。当您想要查询给定
照片
的评论时,请执行一些连接:当您只有评论的 ID 并且您想要查找该评论针对的可评论资源时。 为此,您可能会发现可评论表指定它引用的资源很有帮助。
然后,在从
commentable_type
发现要加入哪个表后,您需要运行第二个查询以从相应的资源表(照片、文章等)获取数据。 您不能在同一个查询中执行此操作,因为 SQL 要求显式命名表; 您无法连接到由同一查询中的数据结果确定的表。诚然,其中一些步骤违反了 Rails 使用的约定。 但是 Rails 约定在正确的关系数据库设计方面是错误的。
A foreign key must reference only one parent table. This is fundamental to both SQL syntax, and relational theory.
A Polymorphic Association is when a given column may reference either of two or more parent tables. There's no way you can declare that constraint in SQL.
The Polymorphic Associations design breaks rules of relational database design. I don't recommend using it.
There are several alternatives:
Exclusive Arcs: Create multiple foreign key columns, each referencing one parent. Enforce that exactly one of these foreign keys can be non-NULL.
Reverse the Relationship: Use three many-to-many tables, each references Comments and a respective parent.
Concrete Supertable: Instead of the implicit "commentable" superclass, create a real table that each of your parent tables references. Then link your Comments to that supertable. Pseudo-rails code would be something like the following (I'm not a Rails user, so treat this as a guideline, not literal code):
I also cover polymorphic associations in my presentation Practical Object-Oriented Models in SQL, and my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Re your comment: Yes, I do know that there's another column that notes the name of the table that the foreign key supposedly points to. This design is not supported by foreign keys in SQL.
What happens, for instance, if you insert a Comment and name "Video" as the name of the parent table for that
Comment
? No table named "Video" exists. Should the insert be aborted with an error? What constraint is being violated? How does the RDBMS know that this column is supposed to name an existing table? How does it handle case-insensitive table names?Likewise, if you drop the
Events
table, but you have rows inComments
that indicate Events as their parent, what should be the result? Should the drop table be aborted? Should rows inComments
be orphaned? Should they change to refer to another existing table such asArticles
? Do the id values that used to point toEvents
make any sense when pointing toArticles
?These dilemmas are all due to the fact that Polymorphic Associations depends on using data (i.e. a string value) to refer to metadata (a table name). This is not supported by SQL. Data and metadata are separate.
Define
Commentable
as a real SQL table, not just an adjective in your Rails model definition. No other columns are necessary.Define the tables
Articles
,Photos
, andEvents
as "subclasses" ofCommentable
, by making their primary key be also a foreign key referencingCommentable
.Define the
Comments
table with a foreign key toCommentable
.When you want to create an
Article
(for instance), you must create a new row inCommentable
too. So too forPhotos
andEvents
.When you want to create a
Comment
, use a value that exists inCommentable
.When you want to query comments of a given
Photo
, do some joins:When you have only the id of a comment and you want to find what commentable resource it's a comment for. For this, you may find that it's helpful for the Commentable table to designate which resource it references.
Then you'd need to run a second query to get data from the respective resource table (Photos, Articles, etc.), after discovering from
commentable_type
which table to join to. You can't do it in the same query, because SQL requires that tables be named explicitly; you can't join to a table determined by data results in the same query.Admittedly, some of these steps break the conventions used by Rails. But the Rails conventions are wrong with respect to proper relational database design.
Bill Karwin 是正确的,外键不能与多态关系一起使用,因为 SQL 并不真正具有本机概念多态关系。 但是,如果您拥有外键的目标是强制引用完整性,您可以通过触发器模拟它。 这与数据库有关,但下面是我最近创建的一些触发器,用于模拟多态关系上外键的级联删除行为:
在我的代码中,
brokerages
表中的一条记录或>agents
表可以与subscribers
表中的记录相关。Bill Karwin is correct that foreign keys cannot be used with polymorphic relationships due to SQL not really having a native concept polymorphic relationships. But if your goal of having a foreign key is to enforce referential integrity you can simulate it via triggers. This gets DB specific but below is some recent triggers I created to simulate the cascading delete behavior of a foreign key on a polymorphic relationship:
In my code a record in the
brokerages
table or a record in theagents
table can relate to a record in thesubscribers
table.假设您想用 SQL 创建一个类似的系统。 在多态关联中,您可以使用这些列创建一个 LIKE 表:
liked_id
指的是图像的 id 或视频的 id。通常,当您创建表时,您会设置一个
外键
并告诉 SQL 数据库该外键将引用哪个表。 在插入行之前,数据库将检查当前外键值是否有效。 例如,如果在外键引用 USER 表的表中,数据库将检查 USER 表中是否存在当前外键。 此验证可确保您的数据库是一致的。假设在上表中,我们以某种方式将
liked_id
设置为外键,数据库如何知道要访问哪个表(IMAGE 或 VIDEO)来验证当前键值? liked_type 不是供数据库管理器使用的,而是供人类读取的,数据库管理器不会读取它来检查要转到哪个表。想象一下,我插入了一个带有
liked_id=333333
的新行,您的数据库管理器将无法验证 IMAGE 表中是否存在
id=333333
。Let's say you want to create a like system in SQL. in polymorphic association you create a LIKE table with those columns:
liked_id
refers to id of image or id of video.Normally when you create a table, you set a
foreign key
and tell the SQL database which table will this foreign key reference. Before you insert the row, your database will check if the current foreign key value is valid or not. for example if in a table where foreign key references USER table, your database will check if the current foreign key exists in the USER table. This verification makes sure your database is consistent.Let's say in the above table, we somehow set the
liked_id
as a FOREIGN KEY, how the database would know which table (IMAGE or VIDEO) to visit to validate the current key value?liked_type
is not for the database manager it is for humans to read it, database manager does not read it to check which table to go.Imagine I inserted a new row with
liked_id=333333
your database manager will not be able to validate if
id=333333
exist in IMAGE table or not.