跳至主要内容

数据库中的一对多关系:完整指南

了解一对多关系是什么、它们如何使用键将表关联起来,以及为何它们对清晰、可靠的关系型数据库设计至关重要。
· 8分钟 阅读

使用 AI 探索

在本教程中,我将解释什么是一对多关系、如何在数据库设计中实现它,以及为保持数据库可扩展且高效而应遵循的最佳实践。

如果您刚开始担任数据库工程师,我建议学习我们的 SQL 关系型数据库入门和 数据库设计课程,学习在定义数据库模式时如何创建关系。

什么是一对多关系?

一对多关系(1:N)描述的是这样一种情况:一个表中的一条记录可以关联到另一个表中的多条记录。通俗地说,就是某个“东西”只存在一次,但可以连接到许多相关的“东西”。

例如,单个客户可以下很多订单,但每个订单只属于一个客户。

您经常会看到使用以下术语来描述一对多关系:

  • 父表: 包含“1”侧记录的表
  • 子表: 包含“多”侧记录的表

在我们的示例中,客户记录在父表中,订单表为子表。

理解一对多关系中的基数

基数(Cardinality)描述两张表之间可以连接多少条记录。它回答如下问题: 

  • 一个客户可以有多少个订单?
  • 客户是否可以在没有任何订单的情况下存在?

一对多关系是不对称的,意味着两张表上的规则不同。在父表中,单条记录可以关联到多条记录;而在子表中,每条记录只能关联到一条记录。

基数还包括最小和最大限制,其中:

  • 最小基数: 允许的最少相关记录数。
  • 最大基数: 允许的最多相关记录数。

例如,某个客户可以有零个或多个订单,但一个订单必须且只能属于一个客户。

这些规则会影响表设计,例如指示某些字段是否可以为空,以及数据库如何严格地强制执行关系。

用 ER 图可视化一对多关系

在数据库设计中,实体-关系(ER)图是一种可视化方式,用来展示表(实体)、它们之间的连接(关系),以及可以关联多少条记录(基数)。

ER 图符号

大多数 ER 图会在连接表的线两端使用特定符号,以准确表示关系。下面是常用的可视化元素:

  • 实体: 画成矩形,表示诸如Customers或Orders之类的表。
  • 关系: 以连线连接实体,指示实体之间如何关联
  • 基数符号:数字1或单根线表示“1”,而∞、*或“乌鸦脚”表示“多”。

简单 ER 图示例

下面的示例展示了Customers表与Orders表之间的关系。一对多关系表明,一个客户可以有多个订单,但每个订单仅属于一个客户。CustomerID出现在Orders表中,用于将其关联回Customers表。

一对多 ER 图

一对多关系的常见示例

以下是您可能会遇到的一对多关系场景示例:

  • 作者 → 图书:一位作者可以写多本书,但每本书只有一位作者(在简单设计中)。
  • 部门 → 员工:一个部门可以有很多员工,但每位员工只属于一个部门
  • 类别 → 产品:一个类别可以包含很多产品,但每个产品只分配到一个类别。

在关系型数据库中实现一对多关系

一对多关系通过主键和外键来实现。本节将展示这些约束如何帮助在数据库中强制执行数据完整性。

主键与外键

主键是唯一标识表中每一行的列,而外键是一个表中的字段或字段集,用于引用另一个表中的主键。

在一对多关系中,“1”侧拥有主键,而“多”侧的表将该键存为外键。外键随后创建两个表之间的链接,并确保“多”侧的每条记录都指向“1”侧的有效记录。

基础 SQL 示例

我们来看如何使用下面的示例在 SQL 中构建一对多关系。

您将创建Customers表,其中CustomerID唯一标识每位客户。这是父表。

-- Create Customers table with CustomerID as primary key
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    CustomerName VARCHAR(100)
);

现在创建Orders表,其中CustomerID为外键。在该表中,每个订单都准确地引用一位客户,它是子表。

-- Create Orders table with CustomerID as a foreign key
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    OrderDate DATE,
    CustomerID INT,
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

一对多 vs. 多对多关系

理解一对多与多对多关系之间的区别对于正确的数据库设计非常重要。下表总结了这些差异:

方面

一对多关系

多对多关系

定义

表 A 中的一条记录可关联到表 B 中的多条记录

表 A 中的记录可关联到表 B 中的多条记录,反之亦然

关系方向

一 → 多(单向)

多 ↔ 多(双向)

对表 B 的规则

表 B 中的每条记录仅关联到表 A 中的一条记录

表 B 中的每条记录可关联到表 A 中的多条记录

常见示例

客户 → 订单部门 → 员工

学生 ↔ 课程产品 ↔ 订单

现实含义

一个客户可以下多个订单,但每个订单只属于一位客户

一名学生可以选修多门课程,而每门课程也可以有多名学生

实现方式

在“多”侧使用单个外键

需要连接(中间)表

外键位置

存放在“多”侧的表中

存放在连接表中(两个外键)

需要注意的是,关系型数据库无法通过单个外键直接存储多对多关系。相反,需要使用连接表(也称为中间表或桥接表)。

例如,您可以使用Enrollments表作为连接表,将Students表与Courses表中的记录安全地关联起来。连接表将多对多关系拆分为两个一对多关系,因此会同时存储指向两张表的外键。这种设计有助于数据库规范化,从而保持高效。

我建议学习我们的SQL 中的数据连接课程,了解 SQL 中的不同连接类型,以及如何在数据库中处理不同的关联表。

一对多关系中的常见错误

以下是一对多关系中您可能遇到的一些常见问题及其排查方法:

  • 把外键放在错误的表中: 当您把外键存放在“1”侧而非“多”侧时,就会在一列中存储多个值或重复列,破坏规范化并使查询变得困难。要解决此问题,请始终将外键放在关系的“多”侧。
  • 将一对多与多对多混淆: 如果在双方实际上都可能有多条相关记录的情况下,仍把关系建模为一对多,就可能导致数据缺失、行重复,或设计僵化无法适应增长的需求。要修复此问题,请始终确认子记录是否可能对应多条记录;若是,则应使用连接表。
  • 无意中允许孤儿记录: 允许子记录在没有有效父记录的情况下存在,会造成数据不一致,例如没有客户的订单或没有部门的员工。为解决此问题,请使用外键约束(如 ON DELETE CASCADE),让数据库自动强制执行有效关系。

设计一对多关系的最佳实践

我建议遵循以下最佳实践,帮助您设计高效且可扩展的一对多关系:

  • 使用清晰一致的命名: 如CustomerID和OrderID那样为主键进行可预期的命名。清晰的命名让关系无需图示也一目了然,并有助于将外键名称与被引用的主键匹配。

  • 强制执行外键约束: 使用外键让数据库保护数据完整性并防止无效或孤儿记录,尤其是在数据规模增长时。

  • 尽早考虑删除行为: 您可以决定在删除父记录时子记录应当如何处理,以避免意外数据丢失或引用断裂。例如,CASCADE可确保在删除父记录时删除所有子记录。

  • 保持设计简洁: 始终避免不必要的表或复杂关系,并让设计服务于当前所需的模型,而非假设的将来场景。请记住,简单的设计更易维护、讲解和扩展。

结语

一对多关系描述的是,一个表中的单条记录可以与另一个表中的多条记录关联,而这些相关记录各自只属于一个父记录。此模式是关系型数据库设计的基础,因为它能使数据井然有序并避免不必要的重复。

推荐学习我们的SQL 助理数据工程师职业路径,学习在 SQL 中设计数据库以高效处理、存储和组织数据。如果您希望提升大数据场景下的数据库管理技能,我们的Snowflake 数据建模入门课程将帮助您掌握维度建模。

Allan Ouko

作者

Allan Ouko

数据科学技术写作者,具备数据分析、商业智能和数据科学的一线实践经验。我撰写以行业为导向的实用内容,涵盖 SQL、Python、Power BI、Databricks 和数据工程,并以真实的分析工作为基础。我的写作兼顾技术深度与业务影响,帮助专业人士将数据转化为自信的决策。

FAQs

一对多关系与多对多有何不同?

在一对多关系中,只有一侧可以有多条相关记录;在多对多关系中,双方都可以,因此需要一个连接表。

在一对多关系中,外键存放在哪个表中?

外键存放在关系的“多”侧表中。

一对多关系可以在没有外键的情况下存在吗?

概念上可以,但在实践中强烈建议使用外键来强制数据完整性并防止无效记录。

如何判断我的数据模型应为一对多还是多对多?

询问双方是否都可能有多条相关记录。若是,则为多对多;否则为一对多。

关系中的基数是什么?

基数描述两张表之间的数值关系。在 1:N 关系中,父表一侧的基数为 1,子表一侧为多。

主题