1NF
、2NF
、3NF
、BCNF
、4NF
、5NF
都是数据库设计中的范式(Normalization),用于确保数据库中的数据结构尽可能地减少冗余,避免更新异常、插入异常、删除异常等问题,从而提高数据的存储效率和一致性。
本篇文章简单讲解下各个范式的含义,以及如何快速记住它们。
1. 第一范式(1NF)
定义:一个关系(表)满足第一范式,意味着表中的每个字段(列)都必须是原子的(atomic)。即每个字段只能包含一个单一的值,而不能是集合、数组或多值。
通俗来说:数据表的每个单元格只能存储一个值,不能有重复的组或多值字段。
问题:如果数据表不满足1NF,可能存在列包含多个值的情况,比如一个列存储多个电话号码。
例子: 假设有一个“学生课程”表,记录学生选修的课程。初始状态可能是这样的:
学生ID | 学生姓名 | 选修课程 |
---|---|---|
1 | 张三 | 数学, 英语 |
2 | 李四 | 物理, 化学 |
这个表不符合1NF,因为“选修课程”列包含多个值。为满足1NF,我们需要将每个学生的每一门课程分开:
学生ID | 学生姓名 | 选修课程 |
---|---|---|
1 | 张三 | 数学 |
1 | 张三 | 英语 |
2 | 李四 | 物理 |
2 | 李四 | 化学 |
这样,每个字段都是原子的。
2. 第二范式(2NF)
定义:一个关系(表)满足第二范式,首先它必须满足第一范式,并且所有非主属性(即不是主键的属性)完全依赖于主键,而不是部分依赖。也就是说,如果主键是由多个列组成的,那么非主属性必须依赖于整个主键,而不能只依赖于主键的某一部分。
通俗来说:如果主键是复合主键(由多个字段组成),那么每个非主键字段必须依赖于整个主键,而不能只依赖其中的一部分。
问题:如果存在部分依赖,就可能会导致冗余数据。例如,一个复合主键的表中,如果有字段只依赖于主键的一部分,那么这些字段的重复值就会导致数据冗余。
例子: 继续考虑“学生课程”表,如果我们加入“教师姓名”字段,这样的表结构如下:
学生ID | 课程ID | 学生姓名 | 选修课程 | 教师姓名 |
---|---|---|---|---|
1 | 101 | 张三 | 数学 | 王老师 |
1 | 102 | 张三 | 英语 | 李老师 |
2 | 101 | 李四 | 物理 | 王老师 |
2 | 103 | 李四 | 化学 | 张老师 |
主键是“学生ID”和“课程ID”的组合。在这个例子中,学生姓名和教师姓名都依赖于学生ID和课程ID,但学生姓名只依赖于学生ID,而教师姓名只依赖于课程ID。因此,我们存在部分依赖,表不满足2NF。
为满足2NF,我们应该将“学生姓名”单独提取到一个“学生”表中,将“教师姓名”提取到一个“课程”表中:
学生表:
学生ID | 学生姓名 |
---|---|
1 | 张三 |
2 | 李四 |
课程表:
课程ID | 选修课程 | 教师姓名 |
---|---|---|
101 | 数学 | 王老师 |
102 | 英语 | 李老师 |
103 | 化学 | 张老师 |
学生选课表:
学生ID | 课程ID |
---|---|
1 | 101 |
1 | 102 |
2 | 101 |
2 | 103 |
这样就解决了部分依赖问题,表满足了2NF。
3. 第三范式(3NF)
定义:一个关系(表)满足第三范式,首先它必须满足第二范式,并且每个非主属性都不传递依赖于主键。也就是说,非主键字段不能依赖于其他非主键字段。
通俗来说:如果一个字段依赖于其他非主键字段,那么我们就有传递依赖。3NF要求消除这种依赖。
问题:传递依赖可能会导致冗余数据和数据一致性问题。
例子: 假设我们在“课程表”中添加了“课程时长”字段,并且规定“教师姓名”依赖于“教师工号”,即:
课程ID | 选修课程 | 教师工号 | 教师姓名 | 课程时长 |
---|---|---|---|---|
101 | 数学 | 1 | 王老师 | 40小时 |
102 | 英语 | 2 | 李老师 | 35小时 |
103 | 化学 | 3 | 张老师 | 50小时 |
在这个表中,“教师姓名”依赖于“教师工号”,而“教师工号”又依赖于主键(课程ID)。因此,“教师姓名”通过“教师工号”传递依赖于课程ID。为了消除这种传递依赖,我们需要将“教师工号”和“教师姓名”提取到单独的“教师”表中:
教师表:
教师工号 | 教师姓名 |
---|---|
1 | 王老师 |
2 | 李老师 |
3 | 张老师 |
这样就消除了传递依赖,表满足了3NF。
4. 巴斯-科德范式(BCNF)
定义:
一个表满足 BCNF(Boyce-Codd Normal Form)要求满足以下条件:
- 必须满足 3NF。
- 对于每一个函数依赖(A → B),A 必须是候选键(candidate key)。也就是说,任何决定其他属性的字段都必须是候选键。
通俗来说:
- 在 3NF 中,我们要求没有传递依赖,而 BCNF 要求每个决定其他字段的字段,必须是候选键。
- 候选键 是指在没有其他列可以作为键的情况下,可以唯一标识表中一行的列。
为什么需要 BCNF?
3NF 有时不能处理一些更复杂的依赖关系,特别是当表中的某些非主属性决定了其他非主属性时,虽然这些属性满足了3NF,但却不能保证表的完美规范化。
BCNF 示例:
假设我们有以下表格,记录了课程、学生、教师和教室的信息:
课程ID | 学生ID | 教师工号 | 教师姓名 | 教室 |
---|---|---|---|---|
101 | 1 | 1 | 王老师 | A101 |
101 | 2 | 1 | 王老师 | A101 |
102 | 1 | 2 | 李老师 | B201 |
102 | 3 | 2 | 李老师 | B201 |
-
假设 课程ID → 教室(即每门课程对应一个教室),教师工号 → 教师姓名(即每个教师工号对应一个教师姓名),课程ID → 教师工号(即每门课程由一个特定的教师授课)。
依赖关系:- 课程ID → 教室
- 课程ID → 教师工号
- 教师工号 → 教师姓名
从上面的表格来看,我们发现 课程ID 确定了 教室 和 教师工号,但 教师工号 又决定了 教师姓名。这意味着 教师姓名 是由 教师工号 确定的,而 教师工号 不是候选键。教师工号 → 教师姓名 的依赖关系违反了 BCNF,因为 教师工号 不是候选键。
解决方案:
为了使表符合 BCNF,我们需要将表拆分为两个表:
1. 课程表:包含课程和教室的信息。
2. 教师表:包含教师工号和教师姓名的信息。
3. 选课表:包含学生ID、课程ID和教师工号的信息。拆分后的表:
-
课程表:
课程ID 教室 101 A101 102 B201 -
教师表:
教师工号 教师姓名 1 王老师 2 李老师 -
选课表:
学生ID 课程ID 教师工号 1 101 1 2 101 1 1 102 2 3 102 2
通过拆分,教师工号 → 教师姓名 的依赖关系被转移到 教师表,而不再出现在原始的表中,从而使表符合 BCNF。
5. 第四范式(4NF)
定义:
一个表满足 第四范式(4NF),要求:
- 必须满足 BCNF。
- 表中不存在多值依赖(multivalued dependency)。
多值依赖 是指一个属性集决定了两个或多个其他属性集,但这些属性集之间没有关系。例如,一个学生可以同时选修多门课程,并且每个学生有多个兴趣爱好,这就构成了多值依赖。
通俗来说:
- 如果一个表中存在两组不相关的多值依赖关系,那么表就不符合 4NF。
- 4NF 的目的是消除由于多值依赖引起的数据冗余。
4NF 示例:
假设有一个表记录了学生的选修课程和兴趣爱好:
学生ID | 选修课程 | 兴趣爱好 |
---|---|---|
1 | 数学 | 篮球 |
1 | 英语 | 游泳 |
2 | 物理 | 篮球 |
在这个表中,学生ID 可以决定 选修课程 和 兴趣爱好,但这两个属性集是独立的,即学生选修课程与学生兴趣爱好之间没有直接关系。因此,表格存在 多值依赖,这是不符合 4NF 的。
解决方案:
为了满足 4NF,我们需要将表拆分为两个表:
- 一个记录学生和选修课程的表。
- 一个记录学生和兴趣爱好的表。
拆分后的表:
-
学生课程表:
学生ID 选修课程 1 数学 1 英语 2 物理 -
学生兴趣表:
学生ID 兴趣爱好 1 篮球 1 游泳 2 篮球
这样,两个独立的多值依赖关系被拆分到不同的表中,消除了冗余数据,符合 4NF。
6. 第五范式(5NF)
定义:
一个表满足 第五范式(5NF),要求:
- 必须满足 4NF。
- 表中不存在任何连接依赖(join dependency)。
连接依赖 是指表中的某些数据不能通过简单的连接来表示,而是需要一些特殊的处理逻辑来恢复数据。这通常发生在多对多关系中的某些复杂数据模式下。
通俗来说:
- 5NF 关注的是 连接依赖,要求每个数据表的拆分是合理的,且所有的拆分都应该能够通过连接操作恢复。
- 它的主要目的是消除由于复杂多对多关系引起的冗余。
5NF 示例:
假设有一个表记录了员工、项目和技能的信息:
员工ID | 项目ID | 技能 |
---|---|---|
1 | 101 | 编程 |
1 | 101 | 设计 |
1 | 102 | 编程 |
2 | 101 | 设计 |
2 | 102 | 编程 |
在这个表中,员工ID、项目ID 和 技能 之间存在复杂的多对多关系。为了保持 5NF,我们需要拆分表格,以便每个表只描述一个单一的多对多关系。
拆分后的表:
- 员工项目表:
员工ID 项目ID 1 101 1 102 2 101 2 102 - 员工技能表:
员工ID 技能 1 编程 1 设计 2 设计 2 编程 - 项目技能表:
项目ID 技能 101 编程 101 设计 102 编程
这样,表格被拆分后,可以通过连接操作恢复所有数据,且不存在任何连接依赖,符合 5NF。
7. 记忆法
1. 使用简化的口诀或记忆法
记忆顺序:
1NF → 2NF → 3NF → BCNF → 4NF → 5NF
记住每个范式的核心概念及其解决的问题:
- 1NF:原子性(数据不能重复,必须是单一值)。
- 2NF:消除部分依赖(每个非主属性都必须完全依赖于主键)。
- 3NF:消除传递依赖(非主属性不能依赖于其他非主属性)。
- BCNF:每个决定因素都是候选键(更严格的3NF)。
- 4NF:消除多值依赖(避免两个不相关属性集之间的依赖)。
- 5NF:消除连接依赖(表拆分后可通过连接恢复,避免复杂的连接依赖)。
记忆法:
“原子完全传递候选,多值连接" —— 用来记住每个范式的解决问题:
- 原子:1NF(原子性)
- 完全:2NF(完全依赖)
- 传递:3NF(传递依赖)
- 候选:BCNF(候选键)
- 多值:4NF(多值依赖)
- 连接:5NF(连接依赖)
2. 关联实例,逐步拆解
通过实例化的方式逐步理解和记忆每个范式的含义。先从一个实际的、带有冗余问题的表开始,逐步通过范式的规范化过程来消除问题。
例如,使用一个简单的表来说明每个范式:
- 1NF:学生ID、课程名、教师信息一行一值,不允许多值列。
- 2NF:从1NF拆解出来,解决了“学生姓名”只依赖“学生ID”这个部分依赖。
- 3NF:从2NF拆解出来,解决了“教师姓名”通过“教师工号”传递依赖的问题。
- BCNF:更严格的要求,消除“教师工号”决定“教师姓名”的问题。
- 4NF:解决了多个独立的多值依赖,学生可以选修多个课程,也可以有多个兴趣爱好,避免这些属性混杂在一起。
- 5NF:消除了复杂的连接依赖,保证了拆分表后能通过连接恢复完整数据。
3. 用简短问题检查范式
用一些快速的检查问题来帮助判断某个表是否符合某个范式:
- 1NF:每个字段是否只有一个值?
- 2NF:表是否有复合主键,并且非主属性完全依赖于主键?
- 3NF:表中是否有非主属性依赖于其他非主属性?
- BCNF:是否所有决定其他属性的字段都是候选键?
- 4NF:是否存在多个独立的多值依赖?
- 5NF:是否所有表的拆分可以通过连接恢复,没有连接依赖?