数据库设计三范式
定义
- 按照《数据库系统概论》中的定义,范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。简单来说,范式就是一张数据表中符合某种设计标准的级别。打个比方:电器的能耗级别有1级,2级,3级等等。符合高一级范式的设计,必定符合低一级范式。
ji
第一范式
- 符合1NF的关系中的每个属性都不可再分。
- 不符合第一范式的例子:
学号 姓名 联系方式 001 张三 zhangsan001@aa.com,15331231 002 李四 lisi002@aa.com,1231313 - 联系方式这个属性包含了邮箱和电话号码,是可以拆分开的
- 如果我们要设计上面的数据表,应该是如下的形式
学号 姓名 邮箱 电话号码 001 张三 zhangsan001@aa.com 15331231 002 李四 lisi002@aa.com 1231313 - 1NF设计可能存在的问题:
- 数据冗余过大
- 插入异常
- 修改异常
- 删除异常
- 例如下面的这个数据表
学号 姓名 系名 系主任 课程名称 分数 11001 张三 计算机 王五 数据结构 90 11001 张三 计算机 王五 C语言 80 11001 张三 计算机 王五 操作系统 90 12002 李四 经济 赵六 高等数学 80 12002 李四 经济 赵六 大学英语 90 12002 李四 经济 赵六 微观经济学 85 - 这个数据表存在几个问题:
- 数据冗余过大
- 每一名学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次
- 插入异常
- 假如学校新开了一个系,但是暂时还没有招收任何学生,那么是无法存储系名与系主任信息的
- 删除异常
- 假如某个系中所有学生的记录都删除,那么系与系主任的数据也随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)
- 修改异常
- 假如某个学生转系了,那么需要修改多条记录中系与系主任的数据来保证数据库的一致性
- 数据冗余过大
第2范式
- 因为仅仅符合1NF范式的数据表设计中存在问题,所以我们需要提高标准,去掉导致上述问题的因素,使其符合更高一级的范式。
- 2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。
几个定义
函数依赖
- 若在一张表中,在属性(或属性组)X的值确定的情况下,必定能确定属性Y的值,那么就可以说Y函数依赖于X,写作 X → Y。也就是说,在一张数据表中,不存在任意两条记录,它们在X属性(或属性组)上的值相同,而在Y属性上的值不同。
- 例如,对于表3中的数据,找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作 学号 → 姓名。
但是反过来,因为可能出现同名的学生,所以有可能不同的两条学生记录,它们在姓名上的值相同,但对应的学号不同,所以我们不能说学号函数依赖于姓名。 - 表中其他的函数依赖关系还有如:
- 系名 → 系主任
- 学号 → 系主任
- (学号,课名) → 分数
完全函数依赖
- 在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X ' → Y 不成立,那么我们称 Y 对于 X 完全函数依赖
- 例如 (学号,课名) → 分数
部分函数依赖
- 假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X
- 例如(学号,课名) P→ 姓名
传递函数依赖
- 假如 Z 函数依赖于 Y,且 Y 函数依赖于 X,那么就称 Z 传递函数依赖于 X
码
- 设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K),那么我们称 K 为候选码,简称为码。
- 可以理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。一张表中可以有超过一个码。(实际应用中为了方便,通常选择其中的一个码作为主码(主键))
- 例如:
对于表3,(学号、课名)这个属性组就是码。该表中有且仅有这一个码。(假设所有课没有重名的情况)
-
回过头来看表3的设计。 根据2NF的定义,判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。判断的方法是:
- 第一步:找出数据表中所有的码。
- 表3的码只有一个: (学号、课名)
- 第二步:根据第一步所得到的码,找出所有的主属性。
- 主属性有两个: 1. 学号 2.课名
- 第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
- 非主属性有四个:姓名、系名、系主任、分数
- 第四步:查看是否存在非主属性对码的部分函数依赖。
- 对于(学号,课名) → 姓名,有 学号 → 姓名,存在非主属性 姓名 对码(学号,课名)的部分函数依赖。
- 对于(学号,课名) → 系名,有 学号 → 系名,存在非主属性 系名 对码(学号,课名)的部分函数依赖。
- 对于(学号,课名) → 系主任,有 学号 → 系主任,存在非主属性 对码(学号,课名)的部分函数依赖。
- 第一步:找出数据表中所有的码。
-
如何消除这些部分函数依赖?
- 可以通过将大数据表拆分成两个或者更多个更小的数据表
- 例如将表3拆分为课程表(学号,课名,分数)与学生表(学号,姓名,系名,系主任)
- 课程表
学号 课程名称 分数 11001 数据结构 90 11001 C语言 80 11001 操作系统 90 12002 高等数学 80 12002 大学英语 90 12002 微观经济学 85 - 学生表
学号 | 姓名 | 系名 | 系主任 |
---|---| ---| ---| ---| ---|
11001 | 张三 | 计算机 | 王五 |
12002 | 李四 | 经济 | 赵六 |
表4
-
这样的改进是否有效?
- 数据冗余过大
- 学生的姓名、系名与系主任,不再像之前一样重复那么多次了 --- 有改进
- 插入异常
- 因为学生表的码是学号,不能为空,所以无法操作 --- 无改进
- 删除异常
- 假如某个系中所有学生的记录都删除,那么系与系主任的数据也随之消失了 --- 无改进
- 修改异常
- 假如某个学生转系了,只需要修改这名学生对应的系名即可 --- 有改进
- 数据冗余过大
仅仅符合2NF的要求,很多情况下还是不够的,而出现问题的原因,在于仍然存在非主属性系主任对于 码 学号的传递函数依赖。
第三范式(3NF)
- 3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。
- 在表4的课程表中,主码为(学号,课名),主属性为学号和课名,非主属性只有一个分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。
- 在表4的学生表中,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖。
- 知道了为什么会有传递函数依赖,那我们就可以通过拆分数据表来解决这个问题:
- 课程表
学号 课程名称 分数 11001 数据结构 90 11001 C语言 80 11001 操作系统 90 12002 高等数学 80 12002 大学英语 90 12002 微观经济学 85 - 学生表
学号 | 姓名 | 系名 |
---|---| ---| ---| ---|
11001 | 张三 | 计算机 |
12002 | 李四 | 经济 | - 系
系名 | 系主任 |
---| ---| ---|
计算机 | 王五 |
经济 | 赵六 |
- 课程表
- 这样的改进是否有效?
- 数据冗余过大
- 数据冗余更少了 --- 有改进
- 插入异常
- 因为系表与学生表目前是独立的两张表,所以不影响 --- 有改进
- 删除异常
- 信息不会丢失 --- 有改进
- 修改异常
- 同2NF
- 数据冗余过大
结论
符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。在实际中,为了应对业务中的需要,往往不会严格地遵循3NF范式,具体的使用场景还是要具体来分析。