如何为表上具有许多 m:n 关系的数据库建模
Posted
技术标签:
【中文标题】如何为表上具有许多 m:n 关系的数据库建模【英文标题】:How to model a database with many m:n relations on a table 【发布时间】:2011-10-28 08:41:13 【问题描述】:我目前正在建立一个具有大量多对多关系的数据库。每个关系都通过链接表建模。示例:
一个人有许多工作,工作由许多人完成。一个人有很多房子,房子被很多人占用。一个人有很多他喜欢的餐厅,餐厅有很多人喜欢这个餐厅。
首先我是这样设计的:
表:Person、Job、House、Restaurant、Person_Job、Person_House、Person_Restaurant。
关系 1 - n:Person -> Person_Job, Person -> Person_House, Person -> Person_Restaurant, Job -> Person_Job, House -> Person_House, Restaurant -> Person_Restaurant。
这很快导致了一个拥挤而复杂的 ER 模型。
为了简化这一点,我将其建模如下:
标签:Person、Job、House、Restaurant、Person_Attributes
关系 1 - n:Person -> Person_Attributes、Job -> Person_Attributes、House -> Person_Attributes、Restaurant -> Person_Attributes
Person_Attributes 表应如下所示: 个人身份 工作编号 房屋编号 餐厅编号
如果存在人-工作关系,我将添加一个如下所示的条目:
P1,J1,空,空
如果存在人-家关系,我将添加如下条目:
P1,空,H1,空
因此第二个示例中的属性表将具有与第一个示例的链接表相加相同的条目数。
这大大简化了 ER 模型,只要我为 personId + jobId、personId + houseId 和 personId + restaurantId 建立索引,我认为不会对性能产生太大影响。
我的问题是: 第二种方法是正确的建模方法吗?如果不是,为什么? 我对性能影响是否正确?如果不是,为什么?
我的意思的 mysql Workbench 示例可以在这里找到:
http://www.2shared.com/file/3GBnodEZ/example.html
【问题讨论】:
你的第一种方法是正确的。 ER 模型没有任何问题。 【参考方案1】:您的设计违反了Fourth Normal Form。您试图在一张表中存储多个“事实”,这会导致异常。
Person_Attributes 表应如下所示:personId jobId houseId restaurantId
因此,如果我与一份工作、一所房子、但两家餐馆相关联,我会存储以下内容吗?
personId jobId houseId restaurantId
1234 42 87 5678
1234 42 87 9876
如果我添加第三家餐厅,我会复制其他列?
personId jobId houseId restaurantId
1234 123 87 5678
1234 123 87 9876
1234 42 87 13579
完成!哦,等等,那里发生了什么?我在添加新餐厅的同时换了工作。现在我错误地与两个工作相关联,但没有办法区分这与正确与两个工作相关联。
另外,即使关联两个工作是正确的,数据不应该是这样的吗?
personId jobId houseId restaurantId
1234 123 87 5678
1234 123 87 9876
1234 123 87 13579
1234 42 87 5678
1234 42 87 9876
1234 42 87 13579
它开始看起来像是 jobId、houseId 和 restaurantId 的所有不同值的Cartesian product。实际上是——因为这个表试图存储多个独立的事实。
正确的关系设计需要为每个多对多关系使用单独的交集表。抱歉,您没有找到快捷方式。
(许多关于规范化的文章都说超过 3NF 的更高范式是深奥的,人们永远不必担心 4NF 或 5NF。让这个例子反驳这种说法。)
关于使用 NULL 的评论:那么您在执行唯一性时遇到了问题,因为 PRIMARY KEY
约束要求所有列都不是 NULL。
personId jobId houseId restaurantId
1234 123 87 5678
1234 NULL NULL 9876
1234 NULL NULL 13579
另外,如果我在上表中添加第二个房子或第二个 jobId,我应该把它放在哪一行?你可能会得到这样的结果:
personId jobId houseId restaurantId
1234 123 87 5678
1234 NULL NULL 9876
1234 42 NULL 13579
现在,如果我取消与 restaurantId 9876 的关联,我可以将其更新为 NULL。但这会留下一行所有 NULL,我真的应该删除它们。
personId jobId houseId restaurantId
1234 123 87 5678
1234 NULL NULL NULL
1234 42 NULL 13579
如果我取消了餐厅 13579 的关联,我可以将其更新为 NULL 并保留该行。
personId jobId houseId restaurantId
1234 123 87 5678
1234 NULL NULL 9876
1234 42 NULL NULL
但我不应该合并行,将 jobId 移动到另一行,前提是该列有空缺吗?
personId jobId houseId restaurantId
1234 123 87 5678
1234 42 NULL 9876
问题是,现在添加或删除关联变得越来越复杂,需要多个 SQL 语句进行更改。您将不得不编写大量繁琐的应用程序代码来处理这种复杂性。
但是,如果您为每个多对多关系定义一个表,则所有各种更改都很容易。您确实需要拥有更多表的复杂性,但这样做会简化您的应用程序代码。
向餐厅添加关联只是将INSERT
添加到 Person_Restaurant 表。删除该关联只是一个DELETE
。与工作或房屋有多少关联并不重要。您可以在每个交集表中定义一个主键约束来强制唯一性。
【讨论】:
记住你的应用可以CREATE TABLE
。【参考方案2】:
您的简化版本不代表正确的关系模型。它更像是一个元数据模型。
您的数据库中的表数应该代表您域中的逻辑实体数。这不应该基于有多少实体太多的任意想法而改变。
【讨论】:
那么为什么它不是一个合适的模型呢?我同意“太多”的想法是任意的,但保持模型尽可能简单和可读(和可理解)却不是。 这不是一个合适的模型,因为它不能准确地反映您尝试建模的情况,您的四个完全正确的答案概述了各种原因。 正如tomfanning所说,请参阅其他更全面的答案。我确实想补充一点,您的原始模型比您的简化版本更更易于理解。任何了解关系设计的人都会在几分钟内了解(并能够查询)您的原始模型。【参考方案3】:我认为第二种方法不正确,因为您的 Person_Attributes 表将包含冗余数据。例如: 假设一个人喜欢 10 家餐馆并从事 2 份工作,拥有 3 所房子,您将拥有多达 10 * 2 * 3 个条目,其中应该是 10 + 2 + 3(在 3 个链接表中......根据方法#1) .想想拥有百万用户的缺点,如果您在 Person_Attributes 表中有超过 3 个属性需要处理...... 所以我会在你的问题中采用方法 1。
比如说你的 Person_Attributes 表有以下条目:
personId | houseId | jobId | restaurantId
------------------------------------------
P1 H1 J1 R1
现在如果这个人喜欢餐厅 R2 和 R3...桌子看起来像
P1 H1 J1 R1
P2 H1 J1 R2
P2 H1 J1 R3
表已经有冗余数据 他稍后添加了 Job J2.. 你的桌子看起来像
P1 H1 J1 R1
P2 H1 J1 R2
P2 H1 J1 R3
P1 H1 J2 R1
P2 H1 J2 R2
P2 H1 J2 R3
现在考虑他添加了另一个家庭 H2.. 等等...你明白我的意思吗?
【讨论】:
抱歉,在第二种情况下您仍然会有 15 个条目,这不会改变。 15 个条目代表 15 个事实。我不知道你怎么能比你的第一个解决方案更好地建模。 是的,我明白你的意思,但这不是我试图建模的方式。属性表将是 P1,H1,NULL,NULL; P2,H1,空,空; P1、NULL、J1、NULL 等…… 您的示例数据不一致:P1 的 1 条记录的 Home 条目为 H1,Home 的第 2 条记录为“Null”。如果我通过 Person= P1 查询,我会选择 Person P1 'Null' 或 'H1' 的正确 Home 值?编写使用返回的 SQL 结果集的代码将非常复杂且效率低下。但是,如果我们写了它,问题仍然存在。考虑 3 个实体之间的关系:如果新条目 J2 R1 和 R2 与 P1 相关..要添加...你如何将它添加到你的表中条目: P1, H1, 'null,'null 和 P1, 'null, J1, 'null 您将如何更新这些条目?【参考方案4】:以我的拙见,我会选择第一个模型。它可能是一个更复杂的模型,但最终,当您从表中提取信息时,它会让事情变得更容易,并且应用程序代码可能会变得更脏或对其他程序员来说更难以阅读。此外,有些作者不建议使用这样的多用途表格。
最后,你必须选择更适合你的东西。我们不了解整个背景,因此无法帮助您做出太多决定。但是,就你所说的而言,我肯定会选择第一个选项。
【讨论】:
这个例子的真实世界情况(当然)更复杂。我不认为这对程序员来说更复杂。引用 Person_Attributes.houseId 可能比 Person_House.houseId 更有意义。仍然想知道这是否会影响性能以及如何影响性能。【参考方案5】:第二种模式从几个角度来看都是一个问题。首先,当一切都进入一个元表时,它可能会产生阻塞问题。其次,由于您无法强制执行外键约束,因此更有可能出现数据完整性问题。以这种方式建模是一种 SQL 反模式。第一个模型是正确的。
【讨论】:
以上是关于如何为表上具有许多 m:n 关系的数据库建模的主要内容,如果未能解决你的问题,请参考以下文章