数据库,范式简介
关系模式与范式
什么是关系模式
- D1×D2×⋅⋅⋅×Dn的子集叫做在域D1,D2,⋅⋅⋅,Dn上的关系,表示为$ R(D_1,D_2,···,D_n )$,其中R就是关系名,n就是目或度。一个关系对应一张二维表,二维表就是关系名。
- 在二维表中的行定义,即对关系的描述称为关系模式。
- 通俗来说,关系,可简单的理解为数据表,而关系模式,则是这张数据表的表结构。
- 关系是关系模式在某一时刻的状态或内容,关系模式是静态的;关系是动态的。
什么是范式(NF)
- 按照经典教材——王珊的《数据库系统概论》中的定义,范式是 “符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。
- 换句话说,所谓范式,即“一张数据表的表结构所符合的某种设计标准的级别“。就类似于购买家电时标明的一级能效、二级能效,数据库的范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计关系型数据库的时候,最多考虑到BCNF就够。
- 符合高一级范式的设计,必定符合低一级范式,如符合2NF的关系模式,必定符合1NF。
第一范式(1NF)
什么是1NF
- 第一范式(1NF)的定义为:符合第一范式(1NF)的关系中的每个属性都不可再分。
- 实际上,1NF是所有关系型数据库的最基本要求。在关系型数据库管理系统(RDBMS),例如SQL Server,Oracle,MySQL中创建数据表的时候,如果数据表的设计不符合这个最基本的要求,那么操作一定是不能成功的。也就是说,只要在RDBMS中已经存在的数据表,一定是符合1NF的。
- 这是一个不符合1NF的表结构:
编号 |
商品名 |
进货 |
销售 |
备注 |
|
|
数量 / 单价 |
数量 / 单价 |
|
编号 |
商品名 |
进货数量 |
进货单价 |
销售数量 |
销售单价 |
备注 |
|
|
|
|
|
|
|
1NF存在的问题
- 但是仅仅符合1NF的设计,仍然会存在数据冗余过大,插入异常,删除异常,修改异常的问题,例如对于下表的设计中:
| 学号 | 姓名 | 系名 | 系主任 | 课名 | 分数 |
| :--: | :--: | :--: | :--: | :--: | :--: |
| 95001 | 李勇 | 数学系 | 张清玫 | 复变函数 | 80 |
| 95001 | 李勇 | 数学系 | 张清玫 | 大学英语 | 90 |
| 95001 | 李勇 | 数学系 | 张清玫 | 复变函数 | 70 |
| 95002 | 刘晨 | 计算机系 | 刘逸 | 数据结构 | 75 |
| 95002 | 刘晨 | 计算机系 | 刘逸 | Java编程 | 85 |
| 95003 | 王敏 | 数学系 | 张清玫 | 复变函数 | 95 |
| 95003 | 王敏 | 数学系 | 张清玫 | 大学英语 | 85 |
- 数据冗余过大:每一名学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次。
- 插入异常:假如学校新建了一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),那么是无法将系名与系主任的数据单独地添加到数据表中的。
- 删除异常:假如将某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)。
- 修改异常:假如李勇从数学系转系到物理系,那么为了保证数据库中数据的一致性,需要修改三条记录中系与系主任的数据。
第二范式(2NF)
什么是第二范式
- 第二范式(2NF)是指每个表必须有主关键字,其他数据元素与主关键字一一对应。通常称这种关系为函数依赖关系,即表中其他数据元素都依赖于主关键字,或称该数据元素惟一地被主关键字所标识。
- 在1NF的基础上,2NF消除了非主属性对于码的部分函数依赖。
函数依赖
- 若在一张表中,在属性(或属性组)X的值确定的情况下,必定能确定属性Y的值,那么就可以说Y函数依赖于X,写作 X → Y。也就是说,在数据表中,不存在任意两条记录,它们在X属性(或属性组)上的值相同,而在Y属性上的值不同。这也就是“函数依赖”名字的由来,类似于函数关系 y=f(x),在x的值确定的情况下,y的值一定是确定的。
- 例如,对于表3中的数据,找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作 学号 → 姓名。但是反过来,因为可能出现同名的学生,所以有可能不同的两条学生记录,它们在姓名上的值相同,但对应的学号不同,所以我们不能说学号函数依赖于姓名。
- 表中其他的函数依赖关系还有如:系名 → 系主任;学号 → 系主任;(学号,课名) → 分数。但以下函数依赖关系则不成立:学号 → 课名;学号 → 分数;课名 → 系主任;(学号,课名) → 姓名。
- 函数依赖具体展开,又有如下三个概念:
- 完全函数依赖:
在一张表中,若 X → Y,且对于 X 的任何一个真子集X'(假如属性组 X 包含超过一个属性的话),X' → Y 不成立,则我们称该种依赖关系为完全函数依赖。
举个例子,如果我们想要知道一个学生某一门课的成绩,则我们必须知道他的X(学号和课名),但我如果只有学号或者课名的话(X的真子集),我都无法得到我想要的结果。即X(学号,课名)→ Y(分数),Y(分数)完全函数依赖于X(学号,课名),故我们称Y完全函数依赖于X。
- 部分函数依赖:
假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X,则我们称该种依赖关系为部分函数依赖。
举个例子,如果我想知道某位学生的姓名,那我知道他的学号就可以了,即X'(学号)→ Y(分数),Y(分数)只函数依赖于X(学号,课名)中的子集X'(学号),故我们称Y部分函数依赖于X。
- 传递函数依赖:
假如 Z 函数依赖于 Y,且 Y 函数依赖于 X (X 不包含于Y,且 Y 不函数依赖于Z的前提条件),那么我们就称 Z 传递函数依赖于 X。
举个例子,如果我想知道某位学生的系主任叫啥,其实我可以依靠学生的学号知道该学生所在的系,进而通过系名,可以得知系主任的名字。但学号其实和系主任之间并不存在直接一代关系的,我们需要以系名作为一个媒介桥梁,将学号与系主任名字之间,建立起联系,则我们称X(学号,课名)→ Y(系主任)之间,存在传递函数依赖。
码
- 设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都__完全函数依赖__于 K,那么我们称 K 为__候选码__,简称为码。
- 可以通俗理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。
- 一张表中可以有超过一个码,但在实际应用中,为了方便,通常选择其中的一个码作为__主码__。
- 例如,(学号,课名)这个属性组就是码。该表中有且仅有这一个码。(假设所有课没有重名的情况)
非主属性
- 包含在任意一个码中的属性称为主属性。则不包含在任何一个码中的属性称为非主属性。
- 例如,学号 与 课名是主属性,其余的属性都是非主属性。
判断是否满足2NF
- 判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。
- 具体的判断方法是:
- 找出数据表中所有的码。
- 根据第一步所得到的码,找出所有的主属性。
- 数据表中,除去所有的主属性,剩下的就都是非主属性了。
- 查看是否存在非主属性对码的部分函数依赖。
- 该数据表中的码为(学号,课名)。
- 主属性有两个:学号 与 课名。
- 非主属性有四个:姓名、系名、系主任、分数。
- 非主属性的依赖关系如下:
a.对于(学号,课名) → 姓名,有 学号 → 姓名,存在非主属性 姓名 对码(学号,课名)的部分函数依赖。
b.对于(学号,课名) → 系名,有 学号 → 系名,存在非主属性 系名 对码(学号,课名)的部分函数依赖。
c.对于(学号,课名) → 系主任,有 学号 → 系主任,存在非主属性 对码(学号,课名)的部分函数依赖。

- 不难看出,该表结构中,存在非主属性对于码的部分函数依赖,最高只符合1NF的要求,不符合2NF的要求。
将1NF变为2NF
- 为了将原为1NF的表变成能够符合2NF的表,我们必须消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表。
- 在拆分的过程中,要达到更高一级范式的要求,这个过程叫做”模式分解“。模式分解的方法不是唯一的,以下是其中一种方法:
- 选课(学号,课名,分数)
- 学生(学号,姓名,系名,系主任)
- 对于选课表,其码是(学号,课名),主属性是学号和课名,非主属性是分数,学号确定,并不能唯一确定分数,课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码 (学号,课名)的部分函数依赖,所以此表符合2NF的要求。
- 对于学生表,其码是学号,主属性是学号,非主属性是姓名、系名和系主任,因为码只有一个属性,所以不可能存在非主属性对于码 的部分函数依赖,所以此表符合2NF的要求。

- 通过模式分解以后新表如下:
学号 |
课名 |
分数 |
95001 |
复变函数 |
80 |
95001 |
大学英语 |
90 |
95001 |
复变函数 |
70 |
95002 |
数据结构 |
75 |
95002 |
Java编程 |
85 |
95003 |
复变函数 |
95 |
95003 |
大学英语 |
85 |
学号 |
姓名 |
系名 |
系主任 |
95001 |
李勇 |
数学系 |
张清玫 |
95002 |
刘晨 |
计算机系 |
刘逸 |
95003 |
王敏 |
数学系 |
张清玫 |
2NF的改进与不足
- 冗余度有改进:学生的姓名、系名与系主任,不再像之前一样重复那么多次了。
- 修改有改进:假如李勇从数学系转系到物理系,只需要修改一条记录的系与系主任的数据即可。
- 插入仍异常:由于在学生表中,学号是码,不能为空,故仍无法插入一个尚无学生的新系信息。
- 删除仍异常:若某个系中所有学生相关的记录都删除,那么所有系与系主任的数据仍然全部丢失。
- 所以说,仅仅符合2NF的要求,很多情况下还是不够的,而出现问题的原因,在于虽然已不存在非主属性对于码的部分函数依赖,但仍然存在非主属性系主任对于码学号的传递函数依赖。为了能进一步解决这些问题,我们还需要将符合2NF要求的数据表改进为符合3NF的要求。
第三范式(3NF)
什么是第二范式
- 第三范式(3NF)就是指表中的所有数据元素,不但要能唯一地被主关键字所标识,而且它们之间还必须相互独立,不存在其他的函数关系。
- 即3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。
将2NF变为3NF
- 对于选课表,主码为(学号,课名),主属性为学号和课名,非主属性只有一个,为分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。
- 对于学生表,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖,所以学生表的设计,不符合3NF的要求。
- 为了让数据表设计达到3NF,我们必须进一步进行模式分解为以下形式:
- 选课(学号,课名,分数)
- 学生(学号,姓名,系名)
- 系(系名,系主任)
- 对于选课表,分析同2NF
- 对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,符合3NF的要求。
- 对于系表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),符合3NF的要求。

- 通过模式分解以后新表如下:
学号 |
课名 |
分数 |
95001 |
复变函数 |
80 |
95001 |
大学英语 |
90 |
95001 |
复变函数 |
70 |
95002 |
数据结构 |
75 |
95002 |
Java编程 |
85 |
95003 |
复变函数 |
95 |
95003 |
大学英语 |
85 |
学号 |
姓名 |
系名 |
95001 |
李勇 |
数学系 |
95002 |
刘晨 |
计算机系 |
95003 |
王敏 |
数学系 |
3NF的改进
- 删除有改进:删除某个系中所有的学生记录,该系的信息不会丢失。
- 插入有改进:插入一个尚无学生的新系的信息,因为系表与学生表目前是独立的两张表,所以不影响。
- 冗余信息有改进:数据冗余更少了。
- 由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。
巴斯范式/鲍依斯-科得范式(BCNF)
什么是BCNF
- 所有非主属性对每一个候选键都是完全函数依赖; 所有的主属性对每一个不包含它的候选键,也是完全函数依赖;没有任何属性完全函数依赖于非候选键的任何一组属性。
- BCNF又称为改进的第三范式,在3NF的基础上,进一步要求,不存在主属性对于码的部分函数依赖和传递函数依赖。
为什么需要BCNF
- 某公司有若干个仓库;
- 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作;
- 一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量。
仓库名 |
管理员 |
物品名 |
数量 |
上海仓 |
张三 |
iPhone |
30 |
上海仓 |
张三 |
iPad |
40 |
北京仓 |
李四 |
iPhone |
50 |
北京仓 |
李四 |
iPad |
60 |
已知函数依赖集:
仓库名 → 管理员,管理员 → 仓库名,(仓库名,物品名)→ 数量码:(管理员,物品名),(仓库名,物品名)
主属性:
仓库名、管理员、物品名
非主属性:
数量
∵ 不存在非主属性对码的部分函数依赖和传递函数依赖。
∴ 此关系模式属于3NF。
- 既然此关系模式已经属于了 3NF,那么这个关系模式是否存在问题呢?我们来看以下几种操作:
- 先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空。
- 某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?——仓库本身与管理员的信息也被随之删除了。
- 如果某仓库更换了管理员,会带来什么问题?——这个仓库有几条物品存放记录,就要修改多少次管理员信息。
- 在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题,仍然不是 ”好“ 的设计。
- 造成此问题的原因:存在着主属性对于码的部分函数依赖与传递函数依赖。(在此例中就是存在主属性【仓库名】对于码【(管理员,物品名)】存在部分函数依赖)
将3NF变为BCNF
- 想要解决上述例子存在的问题,具体的办法就是要在 3NF 的基础上消除主属性对于码的部分与传递函数依赖。
- 将上表进一步进行模式分解,消除仓库名与码之间的依赖关系:
- 仓库(仓库名,管理员)
- 库存(仓库名,物品名,数量)
仓库名 |
物品名 |
数量 |
上海仓 |
iPhone |
30 |
上海仓 |
|
iPad |
北京仓 |
iPhone |
50 |
北京仓 |
iPad |
60 |
- 这样,就不存在之前存在的插入异常,修改异常与删除异常的问题了。
参考文档:
数据库第一二三范式到底在说什么?