1 范式的基本概念
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。有冗余的数据库未必是最好的数据库, 有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。各范式之间的联系如下:
满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。
函数依赖
比如描述一个学生的关系,可以有学号(Sno),姓名(Sname)、系名(Sdept)等几个属性。由于一个学号对应一个学生,一个学生只在一个系学习。因此,学号确定以后,学生的姓名和其所在的系的值 也就被唯一的确定了。这种属性间的依赖关系,类似于数学中的 y=f(x),自变量x确定以后,相应的函数值y也就唯一确定了。
也就是 Sname=f(Sno),Sdept=f(Sno),即Sno函数决定Sname,Sno函数决定Sdept。或者说,Sname和Sdept函数依赖于Sno。记作 Sno→Sname,Sdept→Sname。
X→Y,Y→Z,则称为Z对X传递函数依赖。记为
X→Y,但则称X→Y是非平凡的函数依赖。如:关系R(Sno, Cno)
,依赖关系(Sno, Cno)→Sno
,(Sno, Cno)→Cno
都是平凡函数依赖。
X→Y,但则称X→Y是平凡的函数依赖。如:关系R(Sno, Cno, Grade)
,依赖关系(Sno, Cno)→Grade
是非平凡函数依赖。
完全函数依赖:每一个非主属性,函数依赖于主键中的全部属性,而不能仅依赖主键一部分的属性。因为主键可能有多个属性构成,比如主键有三个属性,某个非主属性仅依赖主键的三个属性中的两个,那么就不能被称为完全函数依赖。
部分函数依赖:X→Y,但Y不完全函数依赖于X,则称Y对X部分函数依赖。
码(key)
关系模式R<U,F>。关系名R,一组属性U,属性组U上的一组数据依赖F。
设K为R中的属性或属性组合。若U完全函数依赖于K,则称K为R的候选码。候选码可能多余一个,则选定其中一个为主码(主键)。
包含在任何一个候选码中的属性,称为主属性。不包含在任何码中的属性称为非主属性或非码属性。
最简单的情况,单个属性是码。最极端的情况,整个属性组U是码,称为全码。
外码:一个属性(或属性组),它不是码,但是它别的表的码,它就是外码。
第一范式(1NF)
定义:数据库表的每一列(也称为属性)都是不可分割的原子数据项,不能是集合,数组,记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中的每个域值只能是实体的一个属性或一个属性的一部分。简而言之,第一范式就是无重复的域。
第二范式(2NF)
定义:在1NF基础上,每一个的非码属性(不在主键中的列),都必须完全函数依赖于候选码。(在1NF基础上,消除 非码属性 对码的部分函数依赖,也就是让非码属性 函数依赖于 候选码中的全部属性)。不能有任何一列与主键没有关系。即一个表只描述一件事情。
举个例子,若有一数据表为(学号, 课程名称,姓名, 年龄, 成绩, 学分)。假设(学号, 课程名称) 是主键,就可能有如下依赖关系:
(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)
可以看出,姓名和年龄不依赖于课程,即 非主属性 不完全依赖于主属性。因此,该主码不满足第二范式的要求。
第三范式(3NF)
定义:在2NF基础上,每个非主属性 不依赖于 其它非主属性(在2NF基础上,消除非码属性对候选码的传递函数依赖)。也就是说,任何非主属性都直接依赖于主属性,不能传递依赖于主属性。即 表中的每一列 只与主键直接相关,而不是间接相关,(表中的每一列只能依赖于主键)。每一个非码属性既不部分依赖于码,也不传递依赖于码。
简而言之,第三范式(3NF)要求:一个关系中不包含 已在其它关系中包含的非主关键字信息。
例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后,就不能再将部门名称、部门简介等 与部门有关的信息,加入到员工信息表中。(减少了冗余)。
假设:“部门名称”加入到员工信息表中
员工信息表(员工ID,姓名,部门编号,部门名称)
会出现,员工ID→部门编号→部门名称,的情况。
即 部门名称(非主属性) 传递依赖于 员工ID(主属性)。
非主属性“部门编号” 依赖于 非主属性“部门名称”
这既不符合每个非主属性 不依赖于 其它非主属性,也不符合任何非主属性都直接依赖于主属性,不能传递依赖于主属性
综上,员工信息表(员工ID,姓名,部门编号,部门名称),不符合第三范式。
还有就是,如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,不然就会有大量的数据冗余。
区分1NF、2NF、3NF:第二范式是说一张表中包含了所种不同的实体属性,那么要必须分成多张表。 第三范式是要求已经分成了多张表,那么一张表中只能有另一张表中的id(外键),而不能有其他的任何信息(其他的信息一律用该外键在另一表查询)。
巴斯-科德范式(BCNF,Boyce-Codd Normal Form)
在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题。BCNF由Boyce与Codd提出,通常被认为是修正的第三范式。
在3NF基础上,任何 非主属性 不能对 主键子集 依赖。(在3NF基础上,消除主属性对候选码的部分函数依赖和传递函数依赖)。
定义:关系模式R<U,F>∈1NF,若X→Y 且Y不是X的子集时 X必含有码,则R<U,F>∈BCNF。也就是说,关系模式R<U,F>中,若每一个决定因素都包含码,则R<U,F>∈BCNF。
由BCNF的定义可以得到结论,一个满足BCNF的关系模式有:
-所有非主属性对每一个码都是完全函数依赖。
-所有主属性对每一个不包含它的码也是完全函数依赖。
-没有任何属性 完全函数依赖于 非码的任何一组属性。
若R∈BCNF,按定义排除了任何属性对码的传递依赖与部分依赖,所以R∈3NF。
注意:若R∈BCN,则R∈3NF。 若R∈3NF,R不一定属于BCNF!
多值依赖
多值依赖是属性之间的一对多关系,记为K→→A。
函数依赖事实上是单值依赖,所以不能表达属性值之间的一对多关系。(有人称函数依赖为多值依赖的特例)
平凡的多值依赖:全集U=K+A,一个K可以对应于多个A,即K→→A。此时整个表就是一组一对多关系。
非平凡的多值依赖:全集U=K+A+B,一个K可以对应于多个A,也可以对应于多个B,A与B互相独立,即K→→A,K→→B。整个表有多组一对多关系,且有:“一”部分是相同的属性集合,“多”部分是互相独立的属性集合。
第4范式(4NF)
当关系R的属性集合X是非平凡多值依赖的域,它就包含关系R的键。则
在BCNF的基础上,消除非平凡且非函数依赖的多值依赖。4NF就是限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖。4NF所允许的多值依赖实际上是函数依赖。
不严谨地说,只要两个独立的1:N联系出现在一个关系中,那么就有可能出现多值依赖。
例如:
一个表中存在三个数据:“课程、学生、先修课”。假设2019级的计算机专业学生想要学习JAVA课程,那么他们需要先学习VB、C#、BS三门课,才可以选择进行JAVA课程。存在关系:
课程→学生
课程→先修课
两个均是1:N的关系,当出现在一张表的时候,会出现大量的冗余。所以就我们需要分解它,减少冗余。分解后如下:
第五范式(5NF)
如果关系模式R中的每一个连接依赖均由R的候选码所隐含,则称此关系模式符合第五范式。(在4NF的基础上,消除不是由候选码所蕴含的连接依赖。)
第五范式有以下要求:必须满足第四范式;表必须可以分解为较小的表,除非那些表在逻辑上拥有与原始表相同的主键。
函数依赖是多值依赖的一种特殊的情况,而多值依赖实际上优势连接依赖的一种特殊情况。但连接依赖不像函数依赖和多值依赖可以由语义直接导出,而是在关系连接运算时才反映出来。存在连接依赖的关系模式仍可能遇到数据冗余及插入、修改、删除异常等问题。
总结
1NF |
↓ 消除非主属性对码的部分函数依赖 |
2NF |
↓ 消除非主属性对码的传递函数依赖 |
3NF |
↓ 消除主属性对码的部分和传递函数依赖 |
BCNF |
↓ 消除非平凡且非函数依赖的多值依赖 |
4NF |
↓ 消除不是由候选码所蕴含的连接依赖 |
5NF |
2 范式的应用实例
下面以一个学校的学生系统为例分析说明,这几个范式的应用。
第一范式(1NF)
数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。
首先我们确定一下要设计的内容包括那些。学号、学生姓名、年龄、性别、课程名称、课程学分、系别、学科成绩,系办地址、系办电话等信息。为了简单我们暂时只考虑这些字段信息。我们对于这些信息,所关心的问题有如下几个方面。
学生有那些基本信息?
学生选了那些课,成绩是什么?
每个课的学分是多少?
学生属于那个系,系的基本信息是什么?
第二范式(2NF)
首先我们考虑,把所有这些信息放到一个表中(学号,学生姓名、年龄、性别、课程、课程学分、系别、学科成绩,系办地址、系办电话)下面存在如下的依赖关系。
(学号, 课程名称) → (姓名, 年龄, 成绩, 学分)
问题分析:
姓名和年龄不依赖于课程,即不完全依赖于主属性,因此不满足第二范式的要求,会产生如下问题:
1.数据冗余:同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。
2.更新异常:
(1)若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。
(2)假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。
3.删除异常 :假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。
解决方案
把选课关系表SelectCourse改为如下三个表:
学生:Student(学号,姓名,年龄,性别,系别,系办地址、系办电话);
课程:Course(课程名称,学分);
选课关系:SelectCourse(学号,课程名称,成绩)。
第三范式(3NF)
接着看上面的学生表Student(学号,姓名,年龄,性别,系别,系办地址、系办电话),关键字为单一关键字"学号",因为存在如下决定关系:
(学号)→ (姓名,年龄,性别,系别,系办地址、系办电话)
但是还存在下面的决定关系:
(学号) → (系别)→(系办地点,系办电话)
即存在非关键字段"系办地点"、"系办电话"对关键字段"学号"的传递函数依赖。
它也会存在数据冗余、更新异常、插入异常和删除异常的情况。
根据第三范式把学生关系表分为如下两个表就可以满足第三范式了:
学生:(学号,姓名,年龄,性别,系别);
系别:(系别,系办地址、系办电话)。
上面的数据库表就是符合1NF,2NF,3NF的,消除了数据冗余、更新异常、插入异常和删除异常。
BCNF的例子
要了解 BCNF 范式,那么先看这样一个问题:
若:
- 某公司有若干个仓库;
- 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作;
- 一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量。
那么关系模式 仓库(仓库名,管理员,物品名,数量) 属于哪一级范式?
已知,
函数依赖集:仓库名 → 管理员,管理员 → 仓库名,(仓库名,物品名)→ 数量
码:(管理员,物品名),(仓库名,物品名)
主属性:仓库名、管理员、物品名
非主属性:数量
因为 不存在 非主属性 对码的部分函数依赖和传递函数依赖,所以 此关系模式属于3NF。
基于此关系模式的关系(具体的数据)可能如图所示:
好,既然此关系模式已经属于了 3NF,那么这个关系模式是否存在问题呢?
我们来看以下几种操作:
- 先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空。
- 某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?——仓库本身与管理员的信息也被随之删除了。
- 如果某仓库更换了管理员,会带来什么问题?——这个仓库有几条物品存放记录,就要修改多少次管理员信息。
从这里我们可以得出结论,在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题,仍然不是 ”好“ 的设计。
造成此问题的原因:存在着主属性对于码的部分函数依赖与传递函数依赖。(在此例中就是存在 主属性【仓库名】对于 码【(管理员,物品名)】的部分函数依赖。
解决办法就是拆分成两个表,要在 3NF 的基础上,消除主属性【仓库名】对于码【(管理员,物品名)】的部分与传递函数依赖。
将原来的仓库(仓库名,管理员,物品名,数量)
修改为:
仓库(仓库名,管理员)
库存(仓库名,物品名,数量)
这样,之前的插入异常,修改异常与删除异常的问题就被解决了。
3 参考文献:
王珊 萨师煊.《数据库系统概论》第四版