数据库三范式示例理解

理解数据库范式和函数依赖是数据库设计的基石。这些概念很抽象,但我们可以通过一个从混乱到有序的实际例子,一步步把它们弄清楚。

我们就用一个学校的“学生选课信息”表作为例子,看看它是如何从一个糟糕的设计,一步步通过范式变得“健康”的。


基础:函数依赖 (Functional Dependency)

在理解范式之前,必须先理解函数依赖,因为它是判断是否符合范式的“尺子”。

通俗解释:在一个关系(表)中,如果通过属性A的值,我们总能唯一地确定属性B的值,那么我们就说“B函数依赖于A”,记作 A -> B

  • A 叫做决定因素 (Determinant)
  • B 叫做依赖部分 (Dependent)

生活化比喻:就像自动售货机。你按下一个按钮(决定因素),就会出来一个特定的商品(依赖部分)。

  • 按钮编号 -> 商品名称
  • 学号 -> 姓名 (知道学号,就能唯一确定姓名)
  • 身份证号 -> {姓名, 年龄, 性别} (知道身份证号,就能唯一确定这些信息)

完全函数依赖 vs. 部分函数依赖
这个概念是理解第二范式的关键,它只在主码是复合码(由多个属性组成)时有意义。

  • 完全函数依赖:一个属性必须依赖于主码的全部属性,而不是部分属性。

    • 例子:主码是 {学号, 课程号}。属性 成绩 依赖于 {学号, 课程号}。因为只知道学号无法确定成绩(不知道是哪门课的),只知道课程号也无法确定成绩(不知道是哪个学生的),必须两者都知道才行。所以,成绩 完全函数依赖{学号, 课程号}
  • 部分函数依赖:一个属性只依赖于主码的一部分属性

    • 例子:主码还是 {学号, 课程号}。属性 姓名 只依赖于 学号,和 课程号 没关系。这就是部分函数依赖

第一范式 (1NF)

目标:确保属性的原子性,即表中的每个单元格都不可再分。

“人话”规则表格里不能有“格中格”。每一列的值都必须是单一值,不能是集合或列表。

例子:一个糟糕的、不符合1NF的表

学号 姓名 选课信息
S01 张三 { (C01, C语言, 李老师), (C02, 数据库, 王老师) }
S02 李四 { (C02, 数据库, 王老师) }

问题:“选课信息”这一列包含了多个课程,是一个集合,不是原子值。这违反了1NF。

如何修正:拆分!把集合拆成多行。

修正后,符合1NF的表

学号 姓名 课程号 课程名 教师名
S01 张三 C01 C语言 李老师
S01 张三 C02 数据库 王老师
S02 李四 C02 数据库 王老师

现在,每个单元格都是单一值了。我们达到了1NF。但是,这个表仍然很糟糕,充满了数据冗余(张三的名字、数据库课程名、王老师的名字都重复了)。这会导致更新异常(比如王老师改名了,你需要修改多行,很容易漏掉)。


第二范式 (2NF)

前提:必须首先满足1NF。
目标:消除部分函数依赖

“人话”规则一张表只说一件事。表中的所有非主属性,都必须完全依赖于整个主码,而不是主码的一部分。

例子:分析我们符合1NF的表

  • 确定主码:要唯一确定一行,只知道 学号 不行(一个学生有多门课),只知道 课程号 也不行(一门课有多个学生)。所以,主码是 {学号, 课程号}
  • 分析函数依赖
    • 姓名 -> 只依赖于 学号。(学号 -> 姓名
    • 课程名 -> 只依赖于 课程号。(课程号 -> 课程名
    • 教师名 -> 只依赖于 课程号。(假设一门课只有一个老师)
  • 发现问题姓名, 课程名, 教师名 这些非主属性,都只依赖于主码的一部分,存在严重的部分函数依赖。这违反了2NF。

如何修正:拆分!把描述不同事物的属性拆分到不同的表中。

  • 跟“学生”相关的信息,单独放一张表。
  • 跟“课程”相关的信息,也单独放一张表。
  • 剩下的“学生和课程的关系”,保留在原表中。

修正后,符合2NF的表

学生表 (Student)

学号 (主码) 姓名
S01 张三
S02 李四

课程表 (Course)

课程号 (主码) 课程名 教师名
C01 C语言 李老师
C02 数据库 王老师

选课表 (SC)

学号 (主码/外码) 课程号 (主码/外码)
S01 C01
S01 C02
S02 C02

现在,数据冗余大大减少。每个表都只描述一件事:学生表只说学生,课程表只说课程。我们达到了2NF。但是,课程表里似乎还有问题…


第三范式 (3NF)

前提:必须首先满足2NF。
目标:消除传递函数依赖

传递函数依赖:如果 A -> BB -> C,那么存在 A -> C 的传递依赖。这里A是主码,B和C都是非主属性。

“人话”规则表里的所有非主属性,都必须直接依赖于主码,不能“隔着”另一个非主属性去依赖主码。

例子:分析我们符合2NF的课程表

课程表 (Course)

课程号 (主码) 课程名 教师名
C01 C语言 李老师
C02 数据库 王老师
  • 假设我们再加一个“教师办公室”列
课程号 (主码) 课程名 教师名 教师办公室
C01 C语言 李老师 办公室A
C02 数据库 王老师 办公室B
  • 分析函数依赖
    • 课程号 -> 课程名 (直接依赖)
    • 课程号 -> 教师名 (直接依赖,假设一门课只有一个老师)
    • 但是,教师名 -> 教师办公室
  • 发现问题教师办公室 并不直接依赖于 课程号。它依赖于 教师名,而 教师名 才依赖于 课程号。这就是传递函数依赖 (课程号 -> 教师名 -> 教师办公室)。这违反了3NF。
  • 这会带来什么问题? 如果王老师同时教了10门课,他的办公室信息就会重复10次。如果他换了办公室,你需要修改10行。

如何修正:再拆分!把“教师”这个独立的事物也拆出去。

修正后,符合3NF的表

课程表 (Course) (现在只说课程)

课程号 (主码) 课程名 教师号 (外码)
C01 C语言 T01
C02 数据库 T02

教师表 (Teacher)

教师号 (主码) 教师名 教师办公室
T01 李老师 办公室A
T02 王老师 办公室B

学生表 (Student) (不变)
选课表 (SC) (不变)

现在,所有的非主属性都直接完全地依赖于它们所在表的主码,没有部分依赖,也没有传递依赖。我们的数据库设计达到了3NF,变得非常健康、不易出错。

总结

范式 “人话”规则 技术规则 解决方法
1NF 表格里不能有“格中格” 属性必须是原子的,不可再分 拆分行(消除重复组)
2NF 一张表只说一件事 消除非主属性对主码的部分函数依赖 拆分表(消除部分依赖)
3NF 属性必须直接依赖主码,不能“隔山打牛” 消除非主属性对主码的传递函数依赖 再拆分表(消除传递依赖)
Author

Cofeesy

Posted on

2025-09-09

Updated on

2025-09-10

Licensed under

Comments