【问题标题】:MySQL - Conditional Foreign Key ConstraintsMySQL - 条件外键约束
【发布时间】:2011-01-01 11:00:31
【问题描述】:

我的应用中有以下comments 表:

comments
--------
id           INT
foreign_id   INT
model        TEXT
comment_text TEXT
...

这个表的想法是为我的应用程序的各个部分存储 cmets - 它可以为博客文章存储 cmets,即:

1|34|blogpost|lorem ipsum...

用户图片:

2|12|picture|lorem ipsum...

等等。

现在,有没有办法强制对此类数据进行 FOREIGN KEY 约束?

即cmets 表中的类似内容:

FOREIGN KEY (`foreign_id`) REFERENCES blogposts (`id`)
-- but only when model='blogpost'

【问题讨论】:

    标签: mysql sql database-design foreign-keys polymorphic-associations


    【解决方案1】:

    您正在尝试进行一种称为多态关联的设计。也就是说,外键可以引用多个相关表中的任何一个中的行。

    但是外键约束必须只引用一个表。您不能根据Comments 表的另一列中的值声明引用不同表的外键。这将违反关系数据库设计的几条规则。

    更好的解决方案是制作一种由 cmets 引用的“超级表”。

    CREATE TABLE Commentable (
      id SERIAL PRIMARY KEY
    );
    
    CREATE TABLE Comments (
      comment_id SERIAL PRIMARY KEY,
      foreign_id INT NOT NULL,
      ...
      FOREIGN KEY (foreign_id) REFERENCES Commentable(id)
    );
    

    您的每个内容类型都将被视为此超表的子类型。这类似于接口的面向对象概念。

    CREATE TABLE BlogPosts (
      blogpost_id INT PRIMARY KEY, -- notice this is not auto-generated
      ...
      FOREIGN KEY (blogpost_id) REFERENCES Commentable(id)
    );
    
    CREATE TABLE UserPictures (
      userpicture_id INT PRIMARY KEY, -- notice this is not auto-generated
      ...
      FOREIGN KEY (userpicture_id) REFERENCES Commentable(id)
    );
    

    在将行插入BlogPostsUserPictures 之前,您必须向Commentable 插入新行以生成新的伪密钥ID。然后,您可以在将内容插入相应的子类型表时使用生成的 id。

    完成所有这些后,您就可以依赖参照完整性约束了。

    【讨论】:

    • 我假设 UserPictures 包含一个引用 User 表的字段 user_id。您如何处理用户删除,以便删除将级联到 Commentable 表?我在这里问过这个问题 - stackoverflow.com/questions/11497149/… - 如果你能解释一下你是如何处理我遇到的这个问题的,我将不胜感激。
    • @MattMcCormick,我不再回答关于 SO 的问题,因为讨厌的版主让参与变得毫无意义。
    • 哦,好的。感谢您过去的参与。我阅读了很多关于单表继承和多态关联的答案,还观看了您在其中一个中引用的演讲中的幻灯片。它帮助我更好地识别数据库中可能导致问题的情况。我已将您的书添加到我的阅读列表中,并且可能会在我的下一本软件书籍中选择它来阅读。
    • 多态关联这个术语对我帮助很大。
    • @BillKarwin 很高兴看到您的上述评论不再适用。
    【解决方案2】:

    在 MySQL 5.7 中,您可以拥有一个多态表并享受多态外键之类的东西!

    需要注意的是,从技术上讲,您需要将其实现为多个列上的多个 FK(每个具有 cmets 的实体一个),但实现可能仅限于 DB 端(即您无需担心这些代码中的列)。

    这个想法是使用 MySQL 的 Generated Columns:

    CREATE TABLE comments (
      id INT NOT NULL AUTO_INCREMENT,
      foreign_id INT,
      model TEXT,
      commented_text TEXT,
      generated_blogpost_id INT AS (IF(model = 'blogpost', foreign_id, NULL)) STORED,
      generated_picture_id INT AS (IF(model = 'picture', foreign_id, NULL)) STORED,
      PRIMARY KEY (id) ,
      FOREIGN KEY (`generated_blogpost_id`) REFERENCES blogpost(id) ON DELETE CASCADE,
      FOREIGN KEY (`generated_picture_id`) REFERENCES picture(id) ON DELETE CASCADE
    )
    

    您可以忽略generated_* 列;它们将在添加或修改 cmets 时由 MySQL 自动填充,并且为它们定义的 FK 将确保数据的一致性。

    显然,它会影响大小要求和性能,但对于某些(大多数?)系统而言,它可以忽略不计,而且通过更简单的设计实现数据一致性是值得付出的代价。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多