【问题标题】:MySQL: Two n:1 relations, but not both at onceMySQL:两个 n:1 关系,但不能同时两个
【发布时间】:2011-03-09 20:58:56
【问题描述】:

对不起标题,很难解释。

我需要一个类似这样的数据模型:

如您所见,一个集合既可以属于用户,也可以属于学校。我的问题:它应该只允许属于用户或学校。但永远不要同时发生。

我该如何解决这个问题?

【问题讨论】:

  • 你用什么来制作图形?
  • 哇!自从我上次尝试以来已经走了很长一段路!
  • @basszero:和我安装时的想法一模一样 :)
  • 我对这个问题和接受的答案都投了赞成票,因为两者都很出色。然而,公认的答案虽然很好,但也有点技术性。因此,我在下面提供了一个链接,对于可能希望看到它的其他人来说,它的技术性较低。 stackoverflow.com/questions/6790671/many-to-two-relationship/…

标签: mysql database-design relational-database polymorphic-associations


【解决方案1】:

您当前的设计称为独占弧,其中sets 表有两个外键,并且需要其中一个不为空。这是实现多态关联的一种方法,因为给定的外键只能引用一个目标表。

另一种解决方案是创建一个公共的“超级表”,users 和 schools 都引用,然后将其用作 sets 的父级。

create table set_owner

create table users 
  PK is also FK --> set_owner

create table schools
  PK is also FK --> set_owner

create table sets 
  FK --> set_owner

您可以认为这类似于 OO 建模中的接口:

interface SetOwner { ... }

class User implements SetOwner { ... }

class School implements SetOwner { ... }

class Set {
  SetOwner owner;
}

你的cmets:

所以 SetOwner 表同时包含 UserID 和 SchoolID,对吗?这意味着我不会被允许为用户和学校拥有相同的 ID。我该如何执行?

让 SetOwners 表生成 id 值。您必须先插入 SetOwners,然后才能插入用户或学校。所以让用户和学校中的 id 不自动递增;只需使用 SetOwners 生成的值:

INSERT INTO SetOwners DEFAULT VALUES; -- generates an id
INSERT INTO Schools (id, name, location) VALUES (LAST_INSERT_ID(), 'name', 'location');

这样一来,学校和用户都不会使用给定的 id 值。

如果我想获取集合的所有者类型,是否需要在我的 SetOwner 表中添加 ownerType 属性?

您当然可以这样做。事实上,可能还有其他列对用户和学校都是通用的,您可以将这些列放在超表 SetOwners 中。这进入了 Martin Fowler 的 Class Table Inheritance 模式。

如果我想获取学校或用户的名称(无论是哪种类型),我可以通过一个查询来完成,还是需要两个查询(第一个获取类型,第二个获取名字)?

您需要加入。如果您从给定的 Set 进行查询并且您知道它属于用户(不是学校),您可以跳过加入 SetOwners 并直接加入用户。连接不一定要通过外键。

SELECT u.name FROM Sets s JOIN Users u ON s.SetOwner_id = u.id WHERE ...

如果您不知道给定集合属于用户还是学校,则必须对两者进行外部联接:

SELECT COALESCE(u.name, sc.name) AS name 
FROM Sets s 
LEFT OUTER JOIN Users u ON s.SetOwner_id = u.id
LEFT OUTER JOIN Schools sc ON s.SetOwner_id = sc.id 
WHERE ...

您知道 SetOwner_id 必须匹配一个或另一个表,Users 或 Schools,但不能同时匹配。

【讨论】:

  • 感谢您的精彩解释。超级表的方法似乎类似于 geofflane 在他的评论中提出的方法。我想我现在明白了这个概念。所以SetOwner 表同时包含 UserID 和 SchoolID,对吗?这意味着我不会被允许为用户和学校拥有相同的 ID。我该如何执行?
  • 另一方面:ERD 目前看起来像这样imgur.com/vD1ln.png。如果我想获取集合的所有者类型,是否需要在我的 SetOwner 表中添加 ownerType 属性?还是有其他方法可以做到这一点?如果我想获取学校或用户的名称(无论是哪种类型),我可以用一个查询来完成,还是需要两个查询(第一个获取类型,第二个获取名称)?
  • 很棒的编辑,谢谢。这就是我喜欢 Stackoverflow 的原因。剩下的问题只有一个:我可以在多用户环境中安全地使用LAST_INSERT_ID(),还是其他用户的查询和插入会干扰当前查询,从而导致 LAST_INSERT_ID 发生变化?
  • 抱歉四重评论:我自己找到了答案。 “生成的 ID 在每个连接的基础上维护在服务器中。” dev.mysql.com/doc/refman/5.1/en/…
  • 是的,如果该函数容易受到竞争条件的影响,它将不会提供任何好处。所有数据库都提供了一种获取当前连接中最后生成的伪键值的方法,即使其他客户端也在生成伪键值。
【解决方案2】:

需要注意的是:您的数据模型中的 Set 是什么并不完全清楚。

我在质疑你的数据模型。

为什么 User -> Sets 和 School -> Sets 必须指向同一张表。如果这些是真正独立的数据集,则不会更加规范化。仅仅因为它们在跟踪的列中有一些相似之处并不意味着它们应该存储在同一个表中。它们在逻辑上是一样的吗?

只需为学校集和用户集创建单独的表。这也将使反向查询更容易,因为您不必检查 Sets 表中的空关系来知道它是真正的 UserSet 还是 SchoolSet。

【讨论】:

  • 感谢您的提示。实际上这些集合(许多词汇组成一个集合)是完全相同的,它们可以有不同的所有者,学校或用户。由于集合永远不会从属于学校移动到属于用户,反之亦然,我可以简单地创建两种类型的集合,并在必要时将数据复制到另一个表。这种方法的问题是,我需要两种类型的词汇,我真的认为这没有多大意义。
  • 您可以在用户/学校和集合之间放置一个 SetOwner 表。然后每个可以“拥有”一个集合的表都会有一个指向 set_owner_id 的 FK 列。 Set 表也会有一个 set_owner_id。所以基本上用户和学校与 SetOwner 具有 1:1 的关系,而 SetOwner 与 Set 的关系是 1:m。然后一切都具有可执行的参照完整性。不过,这将使从 Set 到 Owner(逻辑)变得相当困难。
  • 你的意思是这样的? i.imgur.com/dVRs9.png 虽然我不太明白这是如何解决问题的。
  • 是的,就像那样。它确保给定用户或学校只有一个 SetOwner。它还确保 Set 只能属于单个 SetOwner(这意味着学校或用户,但不能同时属于两者)。如果您通常从学校或用户导航到集合,这将起作用。
【解决方案3】:

您必须使用触发器来强制执行此业务规则,因为 MySQL 具有但不强制执行 CHECK 约束(在任何引擎上)。

【讨论】:

    猜你喜欢
    • 2018-03-31
    • 2013-11-20
    • 2013-12-31
    • 2017-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    相关资源
    最近更新 更多