数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握)

数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握)

  • 前言
  • 一、为什么需要规范化
    • [1. 我们先想一个问题:什么样的数据库表才算好表?](#1. 我们先想一个问题:什么样的数据库表才算好表?)
    • [2. 为什么会出现这些问题?](#2. 为什么会出现这些问题?)
    • [3. 规范化的目标](#3. 规范化的目标)
    • [4. 规范化的核心逻辑](#4. 规范化的核心逻辑)
    • [5. 规范化总结](#5. 规范化总结)
  • 二、函数依赖
    • [1. 函数依赖是什么?](#1. 函数依赖是什么?)
    • [2. 平凡 vs 非平凡函数依赖](#2. 平凡 vs 非平凡函数依赖)
      • [2.1 平凡函数依赖「自己决定自己」](#2.1 平凡函数依赖「自己决定自己」)
      • [2.2 非平凡函数依赖「决定新信息」](#2.2 非平凡函数依赖「决定新信息」)
    • [3. 完全 vs 部分函数依赖](#3. 完全 vs 部分函数依赖)
      • [3.1 完全函数依赖「缺一不可」](#3.1 完全函数依赖「缺一不可」)
      • [3.2 部分函数依赖](#3.2 部分函数依赖)
    • [4. 传递函数依赖](#4. 传递函数依赖)
    • [5. 函数依赖练习题讲解](#5. 函数依赖练习题讲解)
    • [6. 一张图看懂函数依赖关系](#6. 一张图看懂函数依赖关系)
  • 三、什么是码?
    • [1. 码的本质:](#1. 码的本质:)
    • [2. 候选码、主码、主属性](#2. 候选码、主码、主属性)
      • [2.1 候选码](#2.1 候选码)
      • [2.2 主码](#2.2 主码)
      • [2.3 主属性与非主属性](#2.3 主属性与非主属性)
    • [3. 全码、外码](#3. 全码、外码)
      • [3.1 全码](#3.1 全码)
      • [3.2 外码](#3.2 外码)
    • [4. 如何求候选码?](#4. 如何求候选码?)
    • 总结码的分类与核心作用
  • 四、什么是范式?
  • 五、第二范式(2NF)
    • [1. 先搞懂第一范式(1NF)](#1. 先搞懂第一范式(1NF))
    • [2. 1NF的问题](#2. 1NF的问题)
    • [3. 什么是第二范式(2NF)](#3. 什么是第二范式(2NF))
    • [4. 用例子理解2NF](#4. 用例子理解2NF)
    • [5. 如何让表符合2NF?](#5. 如何让表符合2NF?)
    • [6. 2NF的效果和局限](#6. 2NF的效果和局限)
  • 六、第三范式(3NF)
    • [1. 什么是3NF?](#1. 什么是3NF?)
    • [2. 为什么需要3NF?](#2. 为什么需要3NF?)
    • [3. 如何让表满足3NF?](#3. 如何让表满足3NF?)
    • [4. 总结:](#4. 总结:)
    • [5. 对比1NF→2NF→3NF:](#5. 对比1NF→2NF→3NF:)
  • 七、BCNF
  • 八、多值依赖
    • [1. Teaching表的冗余困境](#1. Teaching表的冗余困境)
    • [2. 什么是多值依赖?](#2. 什么是多值依赖?)
    • [3. 多值依赖 vs 函数依赖](#3. 多值依赖 vs 函数依赖)
    • [4. 平凡多值依赖 vs 非平凡多值依赖](#4. 平凡多值依赖 vs 非平凡多值依赖)
    • [5. 多值依赖的特性](#5. 多值依赖的特性)
    • [6. 为什么多值依赖会导致问题?](#6. 为什么多值依赖会导致问题?)
    • [7. 如何解决多值依赖?](#7. 如何解决多值依赖?)
  • 九、4NF
    • [1. 4NF的定义](#1. 4NF的定义)
    • [2. 对比BCNF与4NF](#2. 对比BCNF与4NF)
    • [3. 案例分析:为什么Teaching表不满足4NF?](#3. 案例分析:为什么Teaching表不满足4NF?)
    • [2. 分解为4NF](#2. 分解为4NF)
    • [4. 例题:WSC表的分解](#4. 例题:WSC表的分解)
      • [4.1 原表结构:WSC(W, S, C)](#4.1 原表结构:WSC(W, S, C))
  • 总结(核心概念速记):

前言

  • 在前几期博客中,我们探讨了 SQL 连接查询,单表查询,嵌套查询,集合查询,基于派生表的查询,数据插入,修改与删除,空值的处理,视图,数据库安全性技术等知识点。
  • 从本节开始,我们将深入讲解数据库规范化与五大范式。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482


一、为什么需要规范化

1. 我们先想一个问题:什么样的数据库表才算好表?

  • 假设你要设计一个学校教务系统的学生表,需要记录「学号、学院、院长、课程、成绩」。

如果直接把这些信息堆到一张表里(比如下面这张表),乍一看好像没问题,但用起来就会发现许多的问题:

学号 学院 院长 课程 成绩
S1 信息学院 张明 C1 95
S2 信息学院 张明 C1 90
S3 信息学院 张明 C1 88
...... ...... ...... ...... ......

这张表有什么毛病?

  1. 数据冗余到爆炸:每个学生都重复记录「信息学院」「张明」,如果学院改名或换院长,要改几千条数据,漏改一条就出错!
  2. 插入/删除异常:如果一个新学院刚成立,还没有学生,这张表就没法记录学院和院长(因为学号是必填的);反之,如果某个学院的学生都毕业了,删除学生数据时,学院和院长的信息也会跟着消失。
  3. 更新麻烦:院长名字改了,需要把所有该学院学生的记录都改一遍,工作量大且容易漏。

2. 为什么会出现这些问题?

  • 上面的问题,本质是表中数据之间的「依赖关系」没设计好

什么是「数据依赖 」?简单说就是:一个数据的值决定了另一个数据的值

比如:

  • 学号 → 学院(一个学号只能属于一个学院)
  • 学院 → 院长(一个学院只有一个正职院长)
  • 学号+课程 → 成绩(一个学生选一门课只能有一个成绩)

但在最初的表中,「院长」依赖于「学院」 ,而「学院」又依赖于「学号」。

  • 这种多层依赖导致:
学号 学院 院长 课程 成绩
S1 信息学院 张明 C1 95
S2 信息学院 张明 C1 90
S3 信息学院 张明 C1 88
...... ...... ...... ...... ......
  • 只要号和课程 存在,学院和院长就必须存在(插入异常);
  • 只要学号删除,学院和院长可能跟着消失(删除异常);
  • 学院或院长的修改需要波及所有相关学号(更新异常)。

3. 规范化的目标

规范化的目标 :通过拆分表,消除不合理的数据依赖,让每张表只干一件事,避免冗余和异常。

比如上面的例子,把原来的「大杂烩表」拆成三张表:

学号 学院 院长 课程 成绩
S1 信息学院 张明 C1 95
S2 信息学院 张明 C1 90
S3 信息学院 张明 C1 88
...... ...... ...... ...... ......
  1. 学生表(学号,学院)------ 只记录学生属于哪个学院,学号唯一决定学院;
学号 学院
S1 信息学院
S2 信息学院
S3 信息学院
...... ......
  1. 学院表(学院,院长)------ 只记录学院和对应的院长,学院唯一决定院长;
学院 院长
信息学院 张明
...... ......
  1. 成绩表(学号,课程,成绩)------ 只记录学生选课的成绩,学号+课程唯一决定成绩。
学号 课程 成绩
S1 C1 95
S2 C1 90
S3 C1 88
...... ...... ......

这样拆分后

  • 数据冗余减少:院长名字只在学院表中出现一次;
  • 插入/删除正常:新学院没学生也能记录,学生毕业不影响学院信息;
  • 更新方便:改院长名字只需改学院表中的一条记录。

4. 规范化的核心逻辑

  1. 发现问题:观察表是否存在「数据冗余、插入/删除异常、更新麻烦」;
  2. 分析原因:检查数据之间的依赖关系是否「层级混乱」(比如间接依赖导致连锁反应);
  3. 应用规则:通过「范式」(1NF、2NF、3NF等)逐步拆分表,让每张表满足特定的依赖规则(比如「每个非主键字段只依赖于主键」)。

5. 规范化总结

想象一下,如果没有规范化:

  • 数据库会像一团乱麻,修改一个小数据可能引发连锁错误;

  • 数据冗余浪费存储空间,还可能导致「脏数据」(比如同个学院名有多个版本);

  • 插入和删除操作会受到不必要的限制(比如必须先有学生才能记录学院)。

  • 而规范化就像给数据库「分房间」,每个房间(表)只放一类相关的数据,数据之间的依赖关系清晰简单。

  • 这样数据库才能又快又准地处理数据,这也是关系型数据库设计的核心思想之一。

二、函数依赖

1. 函数依赖是什么?

类比场景:就像「身份证号→姓名」,一个身份证号只能对应一个人,知道身份证号就能确定姓名。

定义 :在表中,如果属性组 X 的值确定后,属性 Y 的值也被唯一确定,就称 X 函数决定 Y (记作 X→Y)。

函数依赖的特点

  • 唯一性:X 相同则 Y 必须相同(如学号相同,学院必须相同)。
  • 语义决定:依赖关系由业务规则决定(如"不允许重名"时,姓名→学号才成立)。

反例

下表中 Sno=S1 对应两个不同的 Sname,违背 Sno→Sname函数依赖

Sno Sname Ssex
S1 张三
S1 李四

2. 平凡 vs 非平凡函数依赖

2.1 平凡函数依赖「自己决定自己」

场景 :在组合键(如「学号+课程号」)中,部分属性决定自身。
例子

  • (学号,课程号)→ 学号
  • (学号,课程号)→ 课程号
    特点:Y 是 X 的一部分,无实际意义,所有表都默认存在。

2.2 非平凡函数依赖「决定新信息」

场景 :组合键决定其他独立属性。
例子

  • (学号,课程号)→ 成绩(成绩是新信息,不在组合键中)

特点 :需要重点关注,是数据库设计的核心

3. 完全 vs 部分函数依赖

3.1 完全函数依赖「缺一不可」

定义 :组合键 X 的所有属性必须同时存在,才能决定 Y (缺少任何一个都不行)。
例子

  • (学号,课程号)→ 成绩
    ✅ 单独学号无法决定成绩(一个学生多门课成绩不同)
    ✅ 单独课程号无法决定成绩(一门课多个学生成绩不同)

标记X → F \overset{F}{\rightarrow} →F Y

3.2 部分函数依赖

定义 :组合键 X 中的某个单属性就能决定 Y ,其他属性多余。
例子

  • (学号,课程号)→ 学院
    ❌ 单独学号就能决定学院(课程号无关)
    标记X → P \overset{P}{\rightarrow} →P Y
    危害 :导致数据冗余(如一个学生选多门课,学院重复记录)。

4. 传递函数依赖

定义 :若 X→YY→Z ,但 Y 不能反推 X ,则 X 传递决定 Z
例子

  • 学号→学院,学院→院长
    ✅ 学号→院长(通过学院间接决定)
    ❌ 学院不能反推学号(一个学院有多个学生)

标记X → T \overset{T}{\rightarrow} →T Z

危害:更新异常(如院长离职,需修改所有该学院学生的记录)。

5. 函数依赖练习题讲解

关键符号注释

  • → F \overset{F}{\rightarrow} →F:完全函数依赖
    例:组合键 (A,B) 必须全用上才能得到 C
  • → P \overset{P}{\rightarrow} →P:部分函数依赖
    例:组合键 (A,B) 中,A 单独就能得到 C,B 多余
  • → T \overset{T}{\rightarrow} →T:传递函数依赖
    例:A 决定 B,B 决定 C,但 B 不能决定 A,所以 A 传递决定 C

练习1:职工关系

题目描述:
给定职工关系表如下,分析其中的函数依赖

职工号 职工名 年龄 性别 单位号 单位名
E1 ZHAO 20 F D3 CCC
E2 QIAN 25 M D1 AAA

函数依赖

  1. 职工号→职工名/年龄/性别/单位号(职工号唯一)
  2. 单位号→单位名(单位不重名)
  3. 职工号 → T \overset{T}{\rightarrow} →T 单位名(通过单位号传递)

练习2:工程管理关系

题目描述:
给定工程管理关系表如下,分析其中的函数依赖

工程号 材料号 数量 开工日期 完工日期 价格
P1 I1 4 200510 200608 250
P1 I2 6 200510 200608 300

函数依赖

  1. (工程号,材料号) → F \overset{F}{\rightarrow} →F 数量(完全依赖,缺一不可)
  2. 工程号→开工日期/完工日期(部分依赖,单独工程号决定)
  3. 材料号→价格(单独材料号决定,与工程无关)

6. 一张图看懂函数依赖关系

分类 类型 核心定义 典型例子 记忆技巧
按内容有无意义 平凡函数依赖 Y 是 X 的一部分(自己决定自己的一部分) (学号, 课程号) → 学号 (A,B) → A "多余的依赖",默认存在,不用管
非平凡函数依赖 Y 不是 X 的一部分(决定了新的信息) (学号, 课程号) → 成绩 (A,B) → C "有用的依赖",重点关注
---------------- ---------------- -------------------------------------------- -------------------------------------------- ----------------------------------
按依赖强度 完全函数依赖 X 的所有属性必须同时存在,才能决定 Y(缺一不可) (学号, 课程号) → F \overset{F}{\rightarrow} →F 成绩 "全靠组合键,少一个都不行"
部分函数依赖 X 中的某个单属性就能决定 Y(其他属性多余) (学号, 课程号) → P \overset{P}{\rightarrow} →P 学院 "部分属性就够了,其他是摆设"
---------------- ---------------- -------------------------------------------- -------------------------------------------- ----------------------------------
按传递性 传递函数依赖 X 先决定 Y,Y 再决定 Z(Y 不能反推 X) 学号 → 学院 → 院长 → 学号 → T \overset{T}{\rightarrow} →T 院长 "拐个弯才能决定,中间有跳板"

三、什么是码?

1. 码的本质:

在数据库中, 就像"身份证号",用来唯一标识一条数据记录。

比如:

  • 一个班级里,每个学生有唯一的"学号",学号就是区分学生的"码"。
  • 一张成绩单里,可能需要"学号+课程号"组合起来,才能唯一确定某个人的某门课成绩,这个组合就是"码"。

2. 候选码、主码、主属性

2.1 候选码

  • 定义 :能唯一标识一条记录的最小属性或属性组合

    • "最小"意味着去掉任何一个属性,就无法唯一标识记录了。
  • 例子

    • 职工表 (见下表)中,"职工号"唯一(E1、E2...不重复),因此职工号是候选码
    职工号 职工名 年龄 性别 单位号 单位名
    E1 ZHAO 20 F D3 CCC
    • 工程表 中,单独"工程号"或"材料号"都有重复(如P1对应I1、I2等),但工程号+材料号组合唯一 ,因此工程号+材料号是候选码
    工程号 材料号 数量 开工日期 完工日期 价格
    P1 I1 4 200510 200608 250

2.2 主码

  • 定义:从多个候选码中选一个作为"主负责人",用来代表该表的记录。

  • 特点

    • 每个表必须有且仅有一个主码
    • 主码的值不能重复、不能为空(就像学号不能重复,也不能没有学号)。
  • 例子

    职工号 职工名 年龄 性别 单位号 单位名
    E1 ZHAO 20 F D3 CCC
    • 职工表中候选码只有"职工号",因此主码就是职工号
    • 如果一个表有多个候选码(如学生表中"学号"和"身份证号"都是候选码),选其中一个作为主码即可。

2.3 主属性与非主属性

  • 主属性包含在任何一个候选码中的属性

    职工号 职工名 年龄 性别 单位号 单位名
    E1 ZHAO 20 F D3 CCC
    • 例如职工表中"职工号"是候选码,因此"职工号"是主属性。
    • 工程表中"工程号"和"材料号"都是候选码的一部分,因此都是主属性。
  • 非主属性不包含在任何候选码中的属性

    • 例如职工表中的"职工名""年龄"等,不能用来唯一标识记录,因此是非主属性。

3. 全码、外码

3.1 全码

  • 定义 :当整个属性组(所有列)共同构成候选码时,称为全码。

  • 场景:表中没有任何单个或部分属性能唯一标识记录,必须用所有属性组合。

  • 例子

    • 关系模式WSC(仓库W,保管员S,商品C)
      • 语义:每个仓库的每个保管员保管所有商品(如W1仓库的S1保管员保管C1、C2、C3)。
      • 分析:
        • 单独"W"无法唯一标识记录(W1对应多个S和C)。
        • "W+S"也无法唯一标识(W1+S1对应多个C)。
        • 必须用W+S+C三者组合 才能唯一确定一条记录,因此全码是W+S+C

3.2 外码

  • 定义 :当前表中的某个属性(或属性组)不是本表的码 ,但是另一个表的主码,称为外码。

  • 作用:用于建立表之间的关联(如职工表和单位表通过"单位号"关联)。

  • 例子

    职工号 职工名 年龄 性别 单位号 单位名
    E1 ZHAO 20 F D3 CCC
    • 职工表中的"单位号"(D3、D1等)不是职工表的码(职工表的码是职工号),但它是"单位表"的主码(假设单位表用单位号唯一标识),因此单位号是职工表的外码

4. 如何求候选码?

步骤:

  1. 找唯一标识的属性/组合:看哪些列或列组合能唯一确定一行数据。
  2. 验证最小性:去掉其中一个属性,看是否还能唯一标识(能的话就不是最小组合)。

实例1:职工表

职工号 职工名 年龄 性别 单位号 单位名
E1 ZHAO 20 F D3 CCC
  • 列:职工号、职工名、年龄、性别、单位号、单位名
  • 唯一列:职工号(无重复)
  • 结论:候选码=职工号

实例2:工程表

工程号 材料号 数量 开工日期 完工日期 价格
P1 I1 4 200510 200608 250
P1 I2 6 200510 200608 300
  • 列:工程号、材料号、数量、开工日期、完工日期、价格
  • 单独列:工程号(重复,如P1出现多次),材料号(重复,如I1出现多次)
  • 组合列:工程号+材料号(唯一,每个组合对应唯一一行)
  • 结论:候选码=工程号+材料号

实例3:WSC表

  • 列:W(仓库)、S(保管员)、C(商品)
  • 单独列:W、S、C均重复(如W1对应多个S和C)
  • 两列组合:W+S(对应多个C),W+C(对应多个S),S+C(对应多个W)
  • 三列组合:W+S+C(唯一)
  • 结论:全码=W+S+C

总结码的分类与核心作用

类型 定义 例子(职工表)
候选码 能唯一标识记录的最小属性/组合 职工号
主码 从候选码中选一个作为代表 职工号(唯一候选码,直接选)
主属性 包含在候选码中的属性 职工号
全码 所有属性共同构成候选码 WSC表中的W+S+C
外码 本表中非码属性,但是另一个表的主码 单位号(对应单位表的主码)

四、什么是范式?

为什么需要范式?

假设你设计了一张"学生选课表",记录学生、课程和成绩,但发现:

  1. 数据冗余:同一个学生的姓名、班级重复出现多次。
  2. 插入异常:如果一个学生还没选课,就无法记录他的姓名和班级。
  3. 更新麻烦:修改学生班级时,需要更新所有相关行,容易漏改。
  4. 删除风险:删除某门课的所有记录,可能连学生信息也一起删掉了。

范式就是一套规则,用来规范表格设计,解决上述问题。

核心思路:通过"分表"(模式分解),让表格更"纯净",减少冗余和异常。

五、第二范式(2NF)

1. 先搞懂第一范式(1NF)

1NF是关系型数据库的最低门槛,它只有一个要求:

  • 表中每个字段(属性)必须是最小单元,不能再拆分

    ✅ 正确示例(职工表):

    职工ID 姓名 年龄
    001 张三 25
    (每个字段都是"不可分割"的数据,比如"姓名"不能拆成"姓"和"名"存放在同一列)

    ❌ 错误示例(科目余额表):

    科目 余额(元/角)
    现金 100/5
    ("余额"拆成了"元"和"角",违反1NF,不能称为合法的关系表)

2. 1NF的问题

即便满足1NF,表结构仍可能有问题。比如:
学生选课表(S-L-C)(仅满足1NF):

学号(Sno) 学院(School) 宿舍(Sloc) 课程号(Cno) 成绩(Grade)
S1 信息学院 2号楼 C3 89
S2 信息学院 2号楼 C2 97
S3 计算机学院 3号楼 C1 86

看起来没问题,但藏着3个大坑:

  • 数据冗余:同一个学院的学生,"学院"和"宿舍"反复重复(如信息学院重复多次)。
  • 插入异常:如果一个学生还没选课(Cno为空),就无法插入这条记录(因为"学号+课程号"是主键,不能有空)。
  • 删除异常:如果删除某门课程的所有记录,可能连学生的学院和宿舍信息也一起删掉了。

3. 什么是第二范式(2NF)

2NF的核心规则

  • 前提:必须先满足1NF。
  • 规则 :表中非主属性(不是主键的字段)必须完全依赖于主键,不能只依赖主键的"一部分"。

回顾完全依赖与部分依赖

  • 主键(候选码):能唯一标识一条记录的字段或字段组合(比如"学号+课程号")。
  • 完全依赖 :非主属性必须依赖于整个主键 ,缺一不可。
    例:成绩(Grade)必须由"学号+课程号"共同决定(一个学生选不同课程有不同成绩)。
  • 部分依赖 :非主属性只依赖于主键的一部分
    例:学院(School)只依赖于"学号"(一个学生属于一个学院,和课程无关),这就是"部分依赖"(依赖主键中的"学号",而非整个"学号+课程号")。

4. 用例子理解2NF

表结构

学号(Sno) 学院(School) 宿舍(Sloc) 课程号(Cno) 成绩(Grade)
S1 信息学院 2号楼 C3 89
S2 信息学院 2号楼 C2 97
S3 计算机学院 3号楼 C1 86
  • 主键:(学号,课程号)(Sno, Cno)
  • 非主属性:学院(School)、宿舍(Sloc)、成绩(Grade)

函数依赖关系

  • 成绩完全依赖主键:(学号, 课程号) → 成绩(必须两个字段一起才能确定成绩)。
  • 学院部分依赖主键:学号 → 学院(只用学号就能确定学院,课程号多余)。
  • 宿舍通过学院传递依赖学号:学号 → 学院 → 宿舍(本质还是部分依赖主键中的学号)。

结论 :存在非主属性(学院、宿舍)对主键的部分依赖 ,所以 S-L-C表只满足1NF,不满足2NF

5. 如何让表符合2NF?

解决思路 :把有部分依赖的字段拆到另一个表,确保每个表的非主属性都完全依赖主键。
拆分后的两张表:

表1:学生选课表(SC)------ 专注成绩

学号(Sno) 课程号(Cno) 成绩(Grade)
S1 C3 89
S2 C2 97
  • 主键:(学号,课程号)
  • 非主属性:成绩(完全依赖主键,符合2NF)。

表2:学生学院表(S-L)------ 专注学院和宿舍

学号(Sno) 学院(School) 宿舍(Sloc)
S1 信息学院 2号楼
S3 计算机学院 3号楼
  • 主键:学号(单个字段为主键)
  • 非主属性:学院、宿舍(完全依赖学号,符合2NF)。

6. 2NF的效果和局限

拆分后的好处

  • 数据冗余减少:学院和宿舍信息只存一次,不再随课程重复。
  • 插入/删除更灵活:学生没选课时,可先在S-L表插入学号、学院等信息;删除课程不影响学生基本信息。

但2NF不是终点

  • S-L表中仍存在"传递依赖":学号 → 学院 → 宿舍(宿舍依赖学院,学院依赖学号),这会导致新的冗余(如学院搬迁,所有该学院学生的宿舍都要修改)。
  • 后续需要3NF进一步优化

六、第三范式(3NF)

2NF解决了「部分函数依赖」的问题 ,但可能还存在「传递函数依赖」的问题,而 3NF就是用来解决这个问题的

1. 什么是3NF?

一句话总结

如果一个表满足 2NF ,并且 非主属性之间不能有「传递依赖」 ,那它就属于 3NF

关键概念

  • 传递依赖 :如果 A → BB → C ,且 B 不能反过来决定 A ,那么 C 就通过 B 传递依赖于 A
    (比如:"学生学号 → 学院 → 学院地址","学院地址"就传递依赖于"学生学号")

2. 为什么需要3NF?

假设我们有一个满足 2NF 的表 S-L(Sno, School, Sloc)学生学号,学院,学院地址):

Sno School Sloc
S1 信息学院 教学楼2
S2 信息学院 教学楼2
S3 计算机学院 教学楼3

这个表存在什么问题?

  1. 数据冗余 :同一个学院的地址重复出现(比如"信息学院"的地址重复多次)。
  2. 更新麻烦:如果信息学院搬了新地址,需要修改所有相关行,漏改就会数据不一致。
  3. 插入/删除异常
    • 插入:如果新成立一个学院但还没有学生,无法插入学院和地址(因为学号是主键,不能为空)。
    • 删除:如果删除某个学院的所有学生,学院和地址信息也会被删掉。

问题的根源:传递函数依赖

  • 表中的函数依赖关系:
    • Sno → School(学号决定学院)
    • School → Sloc(学院决定地址)
    • 因此:Sno → School → Sloc ,即 Sloc(地址)通过 School(学院)传递依赖于 Sno(学号)
    • 这违反了 3NF 的要求(非主属性不能传递依赖于主键)。

3. 如何让表满足3NF?

解决方法:把有传递依赖的表拆分成两张表,消除传递依赖。

拆分后的两张表:
  1. 学生-学院表 S-D(Sno, School)

    Sno School
    S1 信息学院
    S2 信息学院
    S3 计算机学院
    • 主键:Sno
    • 依赖关系:Sno → School(直接依赖,无传递)
  2. 学院-地址表 D-L(School, Sloc)

    School Sloc
    信息学院 教学楼2
    计算机学院 教学楼3
    • 主键:School
    • 依赖关系:School → Sloc(直接依赖,无传递)
拆分后的好处:
  1. 数据冗余减少:学院地址只在 D-L 表中存一次。
  2. 更新方便:修改学院地址时,只需改 D-L 表中的一行。
  3. 插入/删除正常
    • 可以先插入学院和地址(即使没有学生)到 D-L 表。
    • 删除学生时,不影响学院和地址信息。

4. 总结:

  1. 3NF是2NF的升级版
    • 先满足 1NF (属性不可拆分),再满足 2NF (消除部分依赖),最后满足 3NF(消除传递依赖)。
  2. 核心原则
    • 非主属性必须直接依赖于主键,不能通过其他非主属性间接依赖。
  3. 不足
    • 3NF 不能完全消除所有问题(比如多值依赖,需要更高的范式如 BCNF),但对大多数业务场景已足够。

5. 对比1NF→2NF→3NF:

范式 解决的问题 举例(学生相关表)
1NF 属性不可拆分 把"姓名+电话"拆成独立字段
2NF 消除部分依赖 把"学生-课程-学院"拆成学生课程表和学生学院表
3NF 消除传递依赖 把"学生-学院-地址"拆成学生学院表和学院地址表

一句话记忆

  • 1NF:字段不能再分;
  • 2NF:主键能完全决定所有非主属性;
  • 3NF:非主属性之间不能"接力决定"。

七、BCNF

1. BCNF的核心定义

BCNF是比3NF更严格的数据库设计规范 ,它的核心要求就一句话:
每个能"决定"其他属性的属性或属性组(决定因素),都必须包含码(主键或候选键)

用大白话来讲就是:

如果属性X能唯一确定属性Y(即X→Y),那么X必须是"老大"(码),不能是"小弟"(非码属性)。

2. BCNF的三个"必须"特点

特点 3NF BCNF
非主属性依赖 消除了对码的部分依赖和传递依赖 同样满足
主属性依赖 可能存在主属性对码的部分/传递依赖 必须消除主属性对码的部分/传递依赖
决定因素要求 允许非码的决定因素存在 禁止非码的决定因素存在

关键区别

  • 3NF只约束"非主属性",而BCNF同时约束"主属性"和"决定因素"。
  • 可以说,BCNF是3NF的"加强版",解决了3NF可能残留的依赖问题。

3. 通过例子理解BCNF(从符合到不符合)

例子1:Course表(符合BCNF)

复制代码
Course(Cno, Cname, Ccredit, Cpno)
Cno Cname Ccredit Cpno
C001 数据库基础 3 NULL
C002 数据结构 4 C001
C003 操作系统 3 C002
C004 计算机网络 3 C001
C005 软件工程 4 C003
  • :Cno(课程号)
  • 函数依赖
    Cno→Cname(课程号决定课程名)
    Cno→Ccredit(课程号决定学分)
    Cno→Cpno(课程号决定先修课)
  • 分析
    所有决定因素都是码(Cno),满足"决定者必须是老大",所以 Course∈BCNF

例子2:Student表(符合BCNF)

复制代码
Student(Sno, Sname, Ssex, Sbirthdate, Smajor)
Sno Sname Ssex Sbirthdate Smajor
S1 张三 2003-05-12 信息管理与信息系统
S2 李四 2004-02-28 数据科学与大数据技术
S3 王五 2003-11-03 计算机科学与技术
S4 赵六 2004-08-15 软件工程
S5 陈七 2003-07-09 信息安全
  • 业务规则 :学生不重名,所以 候选码有两个:Sno和Sname
  • 函数依赖
    Sno→Sname, Ssex,...(学号决定其他属性)
    Sname→Sno, Ssex,...(姓名决定其他属性)
  • 分析
    无论用Sno还是Sname作为决定因素,它们都是码,因此 Student∈BCNF

例子3:STJ表(不符合BCNF)

复制代码
STJ(S, T, J) (学生,教师,课程)
S T J
001 T1 操作系统
001 T2 微机原理
001 T3 数据结构
002 T4 操作系统
003 T1 操作系统
004 T1 操作系统
...... ...... ......
  • 业务规则
    • 每个教师只教一门课(T→J)
    • 每门课可由多个教师授课
    • 学生选课后,教师和课程相互确定(S+J→T,S+T→J)
  • 候选码:(S,J) 和 (S,T)(两个属性组合才能唯一确定一条记录)
  • 函数依赖
    • (S,J)→T(学生+课程决定教师)
    • (S,T)→J(学生+教师决定课程)
    • T→J(教师决定课程,重点!)
  • 问题
    • 决定因素T(教师)不是码(码是S+J或S+T),但T→J成立。
    • 这违反了BCNF的规则:非码的T居然能决定J
  • 结论STJ∉BCNF,但STJ∈3NF(因为非主属性不存在传递依赖)。

4. BCNF的问题与解决

STJ表的问题(不符合BCNF的后果)

  • 数据冗余:教师T1教课程J1,若有100个学生选T1的课,J1会重复100次。
  • 更新麻烦:若T1改教J2,需修改所有相关记录,否则数据不一致。
  • 插入/删除异常:新教师还没学生选课时,无法插入其授课信息。

解决方法:分解成两个BCNF表

  1. ST表(学生-教师)

    复制代码
    ST(S, T) (候选码:(S,T))
    函数依赖:(S,T)→ 无其他属性(直接依赖码)
  2. TJ表(教师-课程)

    复制代码
    TJ(T, J) (候选码:T)
    函数依赖:T→J(决定因素T是码,符合BCNF)
  • 结果
    • 每个表的决定因素都是码,ST∈BCNF,TJ∈BCNF
    • 数据冗余和异常问题消失。

5. 3NF vs BCNF

  • 3NF:只保证"非主属性不依赖非码",但允许"主属性依赖非码"。
  • BCNF:进一步要求"所有决定因素必须是码",彻底消除主属性和非主属性对非码的依赖。

关系

  • 所有BCNF表都属于3NF,但3NF表不一定属于BCNF。
  • BCNF是函数依赖范围内"最彻底"的规范化,能最大程度避免数据问题。

6. 什么时候需要用BCNF?

  • 当你的表中存在"主属性依赖非码属性"时(如STJ中的T→J),就需要用BCNF规范来分解表。
  • 大部分业务场景中,3NF已足够,但在高并发、数据敏感的系统(如金融、医疗)中,常需进一步优化到BCNF。

八、多值依赖

1. Teaching表的冗余困境

假设我们有一张 课程-教员-参考书表(Teaching),记录每门课程的授课老师和使用的参考书:

课程(C) 教员(T) 参考书(B)
物理 李勇 普通物理学
物理 李勇 光学原理
物理 李勇 物理习题集
物理 王军 普通物理学
物理 王军 光学原理
物理 王军 物理习题集
数学 李勇 数学分析
... ... ...

这张表看起来符合BCNF,却有大问题:

  1. 数据爆炸式冗余
    • 物理课有2个老师,3本参考书,导致 2×3=6条记录,每增加一个老师或参考书,记录数就会相乘增长。
  2. 更新麻烦
    • 若物理课新增一本参考书,需要为每个老师单独新增一条记录(如李勇+王军都要关联新书)。
  3. 插入/删除限制
    • 新增老师时,必须同时关联所有参考书;删除某本参考书时,可能误删其他老师的关联记录。

为什么BCNF解决不了这个问题?

因为这里的问题不是函数依赖(如"课程→教员"不成立,一门课有多个教员),而是 多值依赖(Multiple-Valued Dependency)。

2. 什么是多值依赖?

定义

当表中存在三个属性 X、Y、Z,且满足:

  • 给定一个 X 值,对应的 Y 值与 Z 值无关,即 YZ 各自独立地"多对多"依赖于 X
    这种依赖关系称为 多值依赖 ,记作 X →→ Y(读作"X多值决定Y")。

举个例子

  • X=课程(C)Y=教员(T)Z=参考书(B)
课程(C) 教员(T) 参考书(B)
物理 李勇 普通物理学
物理 李勇 光学原理
物理 李勇 物理习题集
物理 王军 普通物理学
物理 王军 光学原理
物理 王军 物理习题集
数学 李勇 数学分析
  • 对于课程"物理"(X=物理):
    • 教员集合 {李勇, 王军} 只与课程有关,与参考书无关(不管用什么书,教员都是这两人)。
    • 参考书集合 {普通物理学, 光学原理, 物理习题集} 也只与课程有关,与教员无关(不管谁来教,书都是这几本)。
  • 因此:C →→ TC →→ B(课程多值决定教员和参考书)。

3. 多值依赖 vs 函数依赖

类型 依赖关系 例子(Teaching表)
函数依赖 X → Y(X唯一决定Y) 若"课程→学分",则一门课只有一个学分
多值依赖 X →→ Y(X决定一组Y值) 一门课可以有多个教员,且与参考书无关

核心区别

  • 函数依赖是"一对一"或"一对多",多值依赖是"一对多对多"(Y和Z互相独立)。
  • 多值依赖中,Y的取值只与X有关,与Z无关;反之亦然。

4. 平凡多值依赖 vs 非平凡多值依赖

  • 平凡多值依赖 :若 Y是X的子集 (Y⊆X),或 Y∪X=U (Y和X包含所有属性),则依赖无实际意义。
    • 例:表中若有"学生S→→课程 C"(课程是学生的属性),属于平凡依赖。
S C
S1 数学
S1 英语
S2 数学
S2 英语
  • 非平凡多值依赖:Y既不是X的子集,也不包含所有其他属性。
课程(C) 教员(T) 参考书(B)
物理 李勇 普通物理学
物理 李勇 光学原理
物理 李勇 物理习题集
物理 王军 普通物理学
物理 王军 光学原理
物理 王军 物理习题集
数学 李勇 数学分析
  • Teaching表中的 C→→TC→→B 均为非平凡多值依赖(T和B都不是C的子集,且还有其他属性)。

5. 多值依赖的特性

  1. 对称性 :若 X→→Y ,则 X→→Z (Z是除X、Y外的属性)。
    • Teaching表中,C→→T成立,则C→→B必然成立(Z=B)。
课程(C) 教员(T) 参考书(B)
物理 李勇 普通物理学
物理 李勇 光学原理
物理 李勇 物理习题集
物理 王军 普通物理学
物理 王军 光学原理
物理 王军 物理习题集
数学 李勇 数学分析
  1. 传递性 :若 X→→YY→→Z ,则 X→→Z-Y

    • (较复杂,可暂不深究,记住多值依赖可传递即可)
  2. 函数依赖是多值依赖的特例 :若 X→Y ,则必然有 X→→Y

    • 例:"学号→姓名"是函数依赖,也是平凡多值依赖(Y⊆X吗?不,这里Y是姓名,X是学号,所以是特殊的多值依赖)。

6. 为什么多值依赖会导致问题?

Teaching表的问题根源在于:

  • 教员和参考书之间没有直接关系,它们的组合(如"李勇+普通物理学"、"王军+光学原理")是"多余的",仅因课程关联而被迫存在。
  • 这种"无关属性的强行组合"导致数据冗余,而BCNF无法处理这种依赖(BCNF只约束函数依赖)。

7. 如何解决多值依赖?

步骤 :将存在非平凡多值依赖的表分解为两张表,消除依赖。
Teaching表分解后

  1. 课程-教员表(C-T)

    课程(C) 教员(T)
    物理 李勇
    物理 王军
    • 依赖:C→→T(多值依赖存在,但表中仅两列,属于平凡多值依赖,无冗余)。
  2. 课程-参考书表(C-B)

    课程(C) 参考书(B)
    物理 普通物理学
    物理 光学原理
    • 依赖:C→→B(同理,平凡多值依赖)。

效果

  • 记录数从6条减少到2+3=5条(实际业务中数据量越大,优化越明显)。
  • 新增教员或参考书时,只需在对应表中单独操作,无需交叉组合。

九、4NF

1. 4NF的定义

关系模式R满足:所有非平凡多值依赖的"决定因素"X都必须包含码,则R∈4NF。

  • :能唯一确定一条记录的属性或属性组(如"学号"是学生表的码)。
  • 核心要求:不允许存在"非平凡且非函数依赖的多值依赖"。

2. 对比BCNF与4NF

范式 处理对象 要求
BCNF 函数依赖 所有非平凡函数依赖的决定因素是码
4NF 多值依赖 所有非平凡多值依赖的决定因素是码
  • 关系:4NF是BCNF的"升级版",先满足BCNF,再处理多值依赖。

3. 案例分析:为什么Teaching表不满足4NF?

  • 数据示例

    C(课程) T(教师) B(教材)
    物理 李勇 普通物理学
    物理 李勇 光学原理
    物理 王军 普通物理学
    物理 王军 光学原理
  • 问题

    • 存在非平凡多值依赖:C→→T 和 C→→B(T和B独立,且C不是码)。
    • :(C, T, B)(三者组合唯一,但单个C无法确定记录)。
    • 结论:Teaching∈BCNF(无不良函数依赖),但∉4NF(存在非平凡多值依赖且决定因素无码)。

2. 分解为4NF

  • CT表(C, T)

    C(课程) T(教师)
    物理 李勇
    物理 王军
    数学 李勇
    • 多值依赖:C→→T(平凡,因C∪T={C,T}=表属性),允许存在。
    • :(C, T)(但决定因素C虽非码,依赖是平凡的,符合4NF)。
  • CB表(C, B)

    C(课程) B(教材)
    物理 普通物理学
    物理 光学原理
    数学 数学分析
    • 多值依赖:C→→B(平凡,因C∪B={C,B}=表属性),允许存在。
    • 结论:CT和CB均∈4NF,消除了多值依赖带来的冗余。

4. 例题:WSC表的分解

4.1 原表结构:WSC(W, S, C)

W S C
W1 S1 C1
W1 S1 C2
W1 S2 C1
W1 S2 C2
W2 S3 C3
W2 S3 C4
W2 S4 C3
W2 S4 C4
  • 含义:W(仓库)、S(保管员)、C(商品),一个仓库有多个保管员和商品,且保管员与商品无关。
  • 问题
    • 非平凡多值依赖:W→→S 和 W→→C(W不是码,码是(W, S, C))。
    • 分解:拆分为WS(W, S)和WC(W, C),每个表中的多值依赖变为平凡(如W→→S在WS表中是平凡的),满足4NF。

总结(核心概念速记):

核心概念速记
数据库规范化 = 数据依赖分析 + 范式分层约束 + 模式分解实践

  • 规范化目标

    • 消除数据冗余、插入/删除异常、更新异常 ,通过模式分解让表结构更"纯净"。
    • 核心逻辑:发现依赖层级混乱 → 应用范式规则 → 逐步拆分表。
  • 数据依赖体系

    • 函数依赖 :X→Y(X唯一决定Y),分为平凡/非平凡、完全/部分、传递三类。
    • 多值依赖:X→→Y(X决定一组独立的Y值,与其他属性无关)。
  • 范式阶梯(从低到高)

    范式 核心规则 解决的问题
    1NF 属性不可拆分(最低要求) 字段原子性
    2NF 非主属性完全依赖主键(消除部分依赖) 部分依赖导致的冗余
    3NF 非主属性无传递依赖(消除传递依赖) 传递依赖导致的冗余
    BCNF 所有决定因素包含码(强化3NF,约束主属性依赖) 主属性对非码的依赖
    4NF 非平凡多值依赖的决定因素包含码(处理多值依赖) 多值依赖导致的爆炸式冗余

规范化关键对比

问题类型 依赖类型 解决范式 典型分解案例
字段可拆分 --- 1NF 拆"姓名+电话"为独立字段
部分依赖 非主属性依赖主键部分属性 2NF 学生选课表拆分为SC(成绩)和S-L(学院)
传递依赖 非主属性→非主属性→码 3NF 学生学院表拆分为S-D(学生-学院)和D-L(学院-地址)
主属性依赖非码 主属性→非码属性 BCNF STJ表拆分为ST(学生-教师)和TJ(教师-课程)
多值依赖冗余 非平凡多值依赖 4NF Teaching表拆分为CT(课程-教师)和CB(课程-教材)

知识图谱

复制代码
数据库规范化与五大范式  
├─ 规范化基础  
│  ├─ 问题驱动:冗余/异常 → 依赖分析  
│  ├─ 函数依赖:平凡/非平凡、完全/部分、传递  
│  └─ 码体系:候选码/主码/全码/外码  
├─ 范式阶梯  
│  ├─ 1NF:属性原子性  
│  ├─ 2NF:消除部分依赖  
│  ├─ 3NF:消除传递依赖  
│  ├─ BCNF:强化决定因素约束  
│  └─ 4NF:处理多值依赖  
├─ 核心操作  
│  ├─ 模式分解:垂直拆分(按依赖类型)  
│  └─ 依赖验证:通过函数依赖图/多值依赖表  
└─ 典型场景  
   ├─ 学生管理:从1NF到3NF的逐步优化  
   ├─ 课程教学:BCNF与4NF的多表分解  
   └─ 多对多关系:通过全码/外码建立关联  

重点提炼

  1. 函数依赖核心判断

    • 完全依赖:组合键缺一不可(如学号+课程号→成绩)。
    • 传递依赖:中间有"跳板"属性(如学号→学院→院长)。
  2. 范式升级关键点

    • 2NF vs 3NF:前者处理部分依赖,后者处理传递依赖。
    • BCNF vs 3NF:前者约束所有决定因素(含主属性),后者仅约束非主属性。
    • 4NF独特点:专门解决多值依赖导致的"属性独立组合冗余"。
  3. 分解原则

    • 保持函数依赖:分解后原依赖关系不丢失。
    • 无损连接:分解后的表通过自然连接可还原原表数据。
  4. 应用建议

    • 一般业务达到3NF即可满足需求。
    • 高并发或数据敏感系统(如金融、电商)需考虑BCNF/4NF

依赖类型与范式对应表

依赖类型 是否允许存在(各范式) 处理方式
平凡函数依赖 所有范式均允许 无需处理
非平凡函数依赖 需决定因素是码(BCNF+) 分解表使决定因素包含码
部分函数依赖 1NF允许,2NF+禁止 拆分表消除对主键部分依赖
传递函数依赖 1NF/2NF允许,3NF+禁止 拆分表打断传递链
非平凡多值依赖 BCNF允许,4NF禁止 拆分表使多值依赖平凡化

技术演进脉络

复制代码
表设计演进 ------ 大杂烩表(1NF) → 消除部分依赖(2NF) → 消除传递依赖(3NF)  
   ↓                ↓                  ↓  
   ↓                ├─ 强化主属性约束(BCNF) → 处理多值依赖(4NF)  
   ↓                ↓  
应用场景 ------ 简单业务       → 复杂业务(含多对多关系) → 高并发/高冗余场景  

经典例题速解

例题 :判断关系模式R(A,B,C,D),若函数依赖为A→B, B→C,码为A,属于第几范式?
分析

  • 非主属性C通过B传递依赖于A(A→B→C),存在传递依赖。
  • 结论:满足2NF(非主属性完全依赖A),但不满足3NF(存在传递依赖),故R∈2NF。

以上就是这篇博客的全部内容,下一篇我们将继续探索更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482

|--------------------|
| 非常感谢您的阅读,喜欢的话记得三连哦 |

相关推荐
网安INF4 分钟前
RSA加密算法:非对称密码学的基石
java·开发语言·密码学
christine-rr4 分钟前
征文投稿:如何写一份实用的技术文档?——以软件配置为例
运维·前端·网络·数据库·软件构建
蔡蓝9 分钟前
设计模式-观察着模式
java·开发语言·设计模式
大咖分享课21 分钟前
容器安全最佳实践:云原生环境下的零信任架构实施
安全·云原生·架构
异常君22 分钟前
@Bean 在@Configuration 中和普通类中的本质区别
java·spring·面试
jackson凌22 分钟前
【Java学习笔记】Math方法
java·笔记·学习
海尔辛28 分钟前
SQL 基础入门
数据库·sql
你不是我我1 小时前
【Java开发日记】说一说 SpringBoot 中 CommandLineRunner
java·开发语言·spring boot
yuan199971 小时前
Spring Boot 启动流程及配置类解析原理
java·spring boot·后端