【问题标题】:Why no many-to-many relationships?为什么没有多对多关系?
【发布时间】:2025-12-24 23:00:07
【问题描述】:

我是第一次学习数据库和 SQL。在我正在阅读的文本(Oracle 11g:Joan Casteel 的 SQL)中,它说“关系数据库中不存在多对多关系”。我了解我们要避免它们,并且我了解如何创建一个桥接实体来消除它们,但我试图完全理解“不可能存在”的说法。

表示多对多关系实际上在物理上是不可能的吗?

还是因为它导致大量数据重复而效率很低?

在我看来是后一种情况,桥接实体最小化了重复数据。但也许我错过了什么?我还没有找到一个具体的原因(或者更好的例子)来解释为什么要避免多对多关系,无论是在文本中还是在我搜索过的任何其他地方。我整天都在搜索,只发现重复的相同信息:“不要这样做,而是使用桥接实体。”但我想问为什么。 :-)

谢谢!

【问题讨论】:

  • 作者发表此声明的背景是什么?我怀疑这种说法比表面上看到的要多,因为 M-M 关系是一种基本的设计解决方案。也就是说,它们被实现为多个 1-M 关系,也许这就是作者想要表达的意思。
  • Thomas,作者确实解释了桥接实体以及它们如何允许用两个一对多关系表示多对多关系。看起来我的引用在下面引起了很多争议,但我认为它是语义的。您帖子中的关键词是“实施”。显然作者的意思是你不能直接实现 M-M 关系,因为她继续解释如何用 1-M 实现它们。我仍在努力解决它,但到目前为止的答案是有帮助的。
  • 如果你想看看下面的辩论,似乎有一个定义问题。 M-M 关系不能存在于单个外键定义中;这是不可能的,但是......使用链接表,可以创建和维护 M-M 关系,这是对关系数据库系统的合法使用。
  • @Johnson - 对。您不能声明 M-M 关系(即通过约束),但您可以实现等效项。

标签: sql database


【解决方案1】:

考虑一种简单的关系,例如作者和书籍之间的关系。一个作者可以写很多本书。一本书可以有很多作者。现在,如果没有桥接表来解决多对多关系,那么替代方案是什么?您必须将多个 Author_ID 列添加到 Books 表中,每个作者一个。但是你加了多少? 2? 3? 10?无论您选择多少,您最终可能会得到很多稀疏行,其中许多 Author_ID 值为 NULL,并且您很可能会遇到需要“仅再增加一个”的情况。因此,您要么不断修改架构以尝试适应,要么施加一些人为的限制(“任何一本书都不能有超过 3 位作者”)来强制适应。

【讨论】:

  • 感谢您的回复。但是为什么需要多个 Author_ID 列呢?为什么不能创建多行,每个 Author_ID 一个?我想回答我自己的问题...那时您在 book 表中没有好的 PK 候选者,因为 Book_ID 在有多个作者时会重复(除非您将 PK 组合成 Book_ID 和 Author_ID 看起来我猜有点笨拙)。尽管如此,“笨拙”与不可能不同。不管怎样,我想我开始明白了。让我知道这是否有意义:(在下面继续)
  • (续上) 1-M 示例:Publisher -
  • (续上) M-M 示例:Book >-
  • (续上) 规则中断:在 Book 表中,我们有重复的 Book_ID 和 Author_ID。我想这里的问题是,即使你可以使用两者的组合作为 PK ......这将是愚蠢的,因为没有代表一本书的行。您必须合并多行才能获取有关一本书的所有信息。反之亦然(重复 Author 表中的两个值)。不过,这对我来说似乎是可能的。就像一个非常糟糕的主意。即使大量重复且难以解释,所有信息仍然可用。
  • @Johnson:我想我在硕士论文上得到的评论比这还少! :-) 但我认为你现在得到了正确的理解。
【解决方案2】:

在关系数据库中不可能创建涉及两个表的真正多对多关系。我相信当他们说它不存在时,这就是他们所指的。为了实现多对多,您需要一个包含 3 个字段的中间表:一个 ID、一个附加到第一个表的 ID 和一个附加到第二个表的 ID。

不想要多对多关系的原因就像您说的那样,它们的效率非常低,并且管理与关系的每一方相关的所有记录可能很困难,例如,如果您删除一侧的记录关系表和对面表中的记录会怎样?至少在我看来,级联删除是一个滑坡。

【讨论】:

  • 中间表中没有第三个“ID”字段的要求。您只需从另外两个表的 ID 中创建一个复合键。
  • @PatrickMichalina:我喜欢使用单列主键,但复合键确实有其用途。我认为它非常有情境。但是,是的,对于这个非常基本的示例,您可以不使用其他两列作为复合键。
  • 我真的很喜欢这个答案的第一段。几十篇文章、帖子和答案都晦涩难懂。
  • “在关系数据库中不可能创建涉及两个表” - 我最近重新阅读了Codd's seminal paper,Codd 的关系模型中没有任何固有的东西会禁止多对多关系(请记住“关系”不是关系)- Codd 的论文为支持 任意约束 以确保一致性的 RDBMS 实现敞开大门,并且可以包括多对多外键,只是(截至 2021 年)没有现存的 RDMBS 支持原生多对多 FK。
【解决方案3】:

通常(双关语)您会使用链接表来建立多对多

就像 Joe Stefanelli 所描述的那样,假设您有作家和书籍

SELECT * from Author
SELECT * from Books

您将创建一个名为 AuthorBooks 的 JOIN

那么,

SELECT * 
FROM Author a 
JOIN AuthorBooks ab
  on a.AuthorId = ab.AuthorId 
JOIN Books b
  on ab.BookId = b.BookId

希望有帮助。

【讨论】:

  • “通常”是什么意思?我认为在 M:N 关系中,第三张表是必须的。
  • @carloswm85 仅当您希望 RDBMS 强制引用完整性时才需要第三个表。如果您将多个引用打包到每行中的单个列中(例如,使用 Postgre 的 array 类型,或涉及 JSON 或 XML 类型的一些 hack),则不需要这样做 - 只有在您可以保证完整性的情况下才应该这样做通过其他方式引用的参考资料。然后,您可以在查询中使用一些解包方法来合成一个您可以JOIN 使用的虚拟表)。我强调这通常仍然是一个坏主意。
【解决方案4】:

它说“关系数据库中不能存在多对多关系。”

我怀疑作者只是在引起争议。从技术上讲,在 SQL 语言中,没有办法显式声明 M-M 关系。这是向表声明多个 1-M 关系的紧急结果。但是,它是实现 M-M 关系结果的常用方法,并且绝对经常用于在关系数据库管理系统上设计的数据库中。

我还没有找到解释为什么要避免多对多关系的具体原因(或者更好的例子),

它们应该用在合适的地方,这样说起来会更准确。有时,例如 Joe Stafanelli 给出的书籍和作者示例,任何其他解决方案都会效率低下并引入其他数据完整性问题。但是,M-M 关系使用起来更复杂。他们为 GUI 设计者增加了更多的工作。因此,它们应该只在有意义的地方使用。如果您非常确信一个实体永远不应与多个其他实体关联,那么请务必将其限制为 1-M。例如,如果您正在跟踪货件的状态,则每个货件在任何给定时间只能有一个状态。允许货物具有多种状态会使设计过于复杂,而且在逻辑上也没有意义。

【讨论】:

    【解决方案5】:

    他们当然可以(并且确实)存在。在我看来,这听起来像是一个肥皂盒声明。许多业务应用程序都需要它们。

    做得好,它们不会效率低下,也没有重复的数据。

    看看 Facebook。朋友和朋友的朋友之间存在多少多对多的关系?这是一个明确的业务需求。

    “关系数据库中不能存在多对多关系”的陈述。显然是错误的。

    【讨论】:

    • 但它们被实现为两个一对多的关系,我很确定这是问题的重点。
    • 不管怎么看,它仍然是多对多的关系,并且因为只看到一半的关系而声称不存在这样的东西显然是荒谬的。
    • @Jeremy - 是也不是。 SQL 中的基本约束没有明确声明 M-M 关系的规定。 M-M 是同一个中间表的多个 1-M 关系的涌现结果。然而,在 Codd 概述的数据库设计的基本概念中,它们绝对存在。正如马丁所说,我猜作者是故意引起争议,以说明 M-M 的实际实施方式。 (那个或作者是个笨蛋)。
    • 你显然上瘾了。放手吧:) RDBMS 中不能存在直接的多对多关系。您不能将键数组作为列。这就是它无法工作的原因。 (至少不是本地的)
    • @Thomas - 它可能与 Codasyl 或 OO 数据库形成对比,但我在这里已经超出了我的深度,因为我从未使用过。
    【解决方案6】:

    多对多关系实际上非常有用,也很常见。例如,考虑一个允许您将人员分组的联系人管理系统。一个人可以在多个组中,每个组可以有多个成员。

    这些关系的表示需要一个额外的表格——也许这就是你的书真正要表达的意思?在我刚刚给出的示例中,您将有一个 Person 表(id​​、name、address 等)和一个 Group 表(id​​、组名等)。两者都不包含有关谁在哪个组中的信息;为此,您有第三个表(称为 PersonGroup),其中每条记录都包含一个人员 ID 和一个组 ID——该记录表示人员和组之间的关系。

    需要查找组的成员?您的查询可能如下所示(对于 ID=1 的组):

    SELECT Person.firstName, Person.lastName 
    FROM Person JOIN PersonGroup JOIN Group 
    ON (PersonGroup.GroupID = 1 AND PersonGroup.PersonID = Person.ID);
    

    【讨论】:

      【解决方案7】:

      没错。多对多关系被分解为几个一对多关系。所以本质上,不存在多对多关系!

      【讨论】:

        【解决方案8】:

        嗯,当然 M-M 关系确实存在于关系数据库中,并且它们也具有通过桥接表在某种程度上处理的能力,但是随着 M-M 关系程度的增加,它也会增加复杂性,从而导致缓慢的 R-W 周期和延迟。 建议在关系数据库中避免这种复杂的 M-M 关系。图数据库是最好的选择,并且擅长处理对象之间的多对多关系,这就是为什么社交网站使用图数据库来处理用户和朋友、用户和事件等之间的 M-M 关系。

        【讨论】:

          【解决方案9】:

          让我们虚构书籍和销售表之间的关系(多对多关系)。假设您正在购买书籍,并且您购买的每本书都需要为该书籍生成发票编号。还假设一本书的发票编号可以代表对同一客户的多次销售(实际上并非如此,但我们假设一下)。我们在书籍和销售实体之间有多对多的关系。 现在如果是这样的话,鉴于我们已经购买了 3 本书,因为理论上所有书籍都具有相同的发票编号,我们如何才能获得仅 1 本书的信息?这引入了我猜想使用多对多关系的主要问题。现在,如果我们在 Books 和 sales 之间添加一个桥接实体,使得每本书售出的只有 1 个发票号码,那么无论购买了多少本书,我们仍然可以正确识别每本书。

          【讨论】:

            【解决方案10】:

            在多对多关系中存在明显的冗余以及插入、更新和删除异常,应通过桥表将其转换为2个一对多关系来消除。

            【讨论】:

              【解决方案11】:

              数据库设计中不应存在 M:N 关系。它们效率极低,不适合功能数据库。具有多对多关系的两个表(实体)(飞机、机场;教师、学生)不能都是彼此的孩子,如果没有相交的表,就无法放置外键。飞机-> 航班 学生。

              交叉点表为依赖于其他两个表的实体提供了一个位置,例如,一个年级需要一个班级和一个学生,一个航班需要一个飞机和一个机场。多对多关系隐藏数据。交集表显示这些数据并创建可以更容易理解和使用的一对多关系。所以,问题来了,航班应该在飞机上还是在机场的哪张桌子上。两者都不是,它们应该是交集表 Flight 中的外键。

              【讨论】:

              • 您的示例是一个奇怪的选择,因为 Flight 显然是一个拥有自己数据的实体(例如 routeIdtakeOffTime),并且需要通过 FK 直接引用(例如Ticket)。一个更好的例子是没有额外数据的简单桥接表。有些关系在语义上是 M:N,因此声称它们不应该存在于数据库设计中是一种误导——您的答案需要区分语义和实现。