首先要明白”范式(NF)”是什么意思。按照教材中的定义,范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。很晦涩吧?实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别。就像家里装修买建材,最环保的是E0级,其次是E1级,还有E2级等等。数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计[关系型数据库]的时候,最多考虑到BCNF就够。符合高一级范式的设计,必定符合低一级范式,例如符合2NF的关系模式,必定符合1NF。
第一范式
规则:数据库中的每一列都是不可再分割的基础数据
举例: 地址这个字段 如果我们业务中只是当一个备注字段 那么做为一个字段也没有什么问题,但如果我们要对地址进行省、市、区做分布分析,那么显然把地址做成一列就违反了第一范式。
符合1NF的关系(你可以理解为数据表。“关系模式”和“关系”的区别,类似于面向对象程序设计中”类“与”对象“的区别。”关系“是”关系模式“的一个实例,你可以把”关系”理解为一张带数据的表,而“关系模式”是这张数据表的表结构。1NF的定义为:符合1NF的关系中的每个属性都不可再分。表1所示的情况,就不符合1NF的要求。
表1
实际上,1NF是所有关系型数据库的最基本要求,你在关系型数据库管理系统(RDBMS),例如SQL Server,Oracle,MySQL中创建数据表的时候,如果数据表的设计不符合这个最基本的要求,那么操作一定是不能成功的。也就是说,只要在RDBMS中已经存在的数据表,一定是符合1NF的。如果我们要在RDBMS中表现表中的数据,就得设计为表2的形式:
表2
但是仅仅符合1NF的设计,仍然会存在数据冗余过大,插入异常,删除异常,修改异常的问题,例如对于表3中的设计:
表3
每一名学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次——数据冗余过大
假如学校新建了一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),那么是无法将系名与系主任的数据单独地添加到数据表中去的 (注1)——插入异常
注1:根据三种关系完整性约束中实体完整性的要求,关系中的码(注2)所包含的任意一个属性都不能为空,所有属性的组合也不能重复。为了满足此要求,图中的表,只能将学号与课名的组合作为码,否则就无法唯一地区分每一条记录。
注2:码:关系中的某个属性或者某几个属性的组合,用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)。
假如将某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)。——删除异常
假如李小明转系到法律系,那么为了保证数据库中数据的一致性,需要修改三条记录中系与系主任的数据。——修改异常。
正因为仅符合1NF的数据库设计存在着这样那样的问题,我们需要提高设计标准,去掉导致上述四种问题的因素,使其符合更高一级的范式(2NF),这就是所谓的“规范化”。
第二范式
规则:数据据库中的非主键字段 对任一候选主码的所有列 都是完全依赖,不存在部份依赖。
当一个表有多种组合或单个列可以唯一区分行时,则这个组合列或单列就可以称为候选键,主键即是从候选键中选中一种可以唯一区分行的组合。大多数情况下,你可以理解为候选键就是我们表中定义的主键
第二范式主要针对联合主键情况
举例:(学号 课程编号 课程名称 成绩) 这样一个成绩表 显然 主键是(学号,课程编号)组合列, 成绩列是对 (学号,课程编号) 完全依赖 没有问题。但显然课程名称列只依赖来课程编号列就存在了部份依赖,所以这个设计不符合2nf 需要把课程名称列移除
函数依赖关系:
存在关系:
R(学号,姓名,性别,班级,班主任,课号,课名,成绩)
主键:学号,课号
主属性:{学号,课号}
非主属性:{姓名,性别,班级,班主任,课名,成绩}
- 完全函数依赖,{学号,课号}-->成绩,{学号,课号}可以决定成绩,但只有学号或是只有课号无法决定成绩。
- 部分函数依赖,{学号,课号}-->姓名,只有学号就能决定姓名 (课号是冗余的) 。
- 传递函数依赖,{学号,课号}-->班主任,班主任依赖于班级,因班级依赖于学号,所以班主任间接依赖于学号。
第二范式(2NF)在关系理论中的严格定义我这里就不多介绍了(因为涉及到的铺垫比较多),只需要了解2NF对1NF进行了哪些改进即可。其改进是,2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。接下来对这句话中涉及到的四个概念——“函数依赖”、“码”、“非主属性”、与“部分函数依赖”进行一下解释。
终于可以回过来看2NF了。首先,我们需要判断,表3是否符合2NF的要求?根据2NF的定义,判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。判断的方法是:
- 第一步:找出数据表中所有的码。
- 第二步:根据第一步所得到的码,找出所有的主属性。
- 第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
- 第四步:查看是否存在非主属性对码的部分函数依赖。
对于表3,根据前面所说的四步,我们可以这么做:
表4表示了模式分解以后新的数据
表4
(这里还涉及到一个如何进行模式分解才是正确的知识点,先不介绍了)
现在我们来看一下,进行同样的操作,是否还存在着之前的那些问题?
- 李小明转系到法律系
只需要修改一次李小明对应的系的值即可。——有改进 - 数据冗余是否减少了?
学生的姓名、系名与系主任,不再像之前一样重复那么多次了。——有改进 - 删除某个系中所有的学生记录
该系的信息仍然全部丢失。——无改进 - 插入一个尚无学生的新系的信息。
因为学生表的码是学号,不能为空,所以此操作不被允许。——无改进
所以说,仅仅符合2NF的要求,很多情况下还是不够的,而出现问题的原因,在于仍然存在非主属性系主任对于码学号的传递函数依赖。为了能进一步解决这些问题,我们还需要将符合2NF要求的数据表改进为符合3NF的要求。
第三范式
规则:表中不存在非主键字段对主键字段的传递依赖
举例:学号 姓名 系名 系主任
系主任与学号是没有直接依赖关系的,是通过系名传递依赖的 所以这个设计符合2nf但不符合3nf
为什么要遵守3nf 是因为 遵守3nf能消除插入异常、修改异常和删除异常。
我们具体来说说这三种异常
1、插入异常: 3nf的例子来说 如果学校决定新增加一个 计算机系 但还没有招生 那这个计算机系就不能录入系统了。
2、修改异常: 3nf的例子来说 如果计算机系 换了一个系主任 则需要修改的行特别多,需要将这个系所有学生的行都需要修改
3、删除异常: 2nf例子来说 假如这门课所有学生都毕业了且新生也没有学生选修。那删除成绩表的时候 就会把这门课也删除了
第三范式(3NF) 3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。也就是说, 如果存在非主属性对于码的传递函数依赖,则不符合3NF的要求。
接下来我们看看表4中的设计,是否符合3NF的要求。
对于选课表,主码为(学号,课名),主属性为学号和课名,非主属性只有一个,为分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。
对于学生表,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖,所以学生表的设计,不符合3NF的要求。。
为了让数据表设计达到3NF,我们必须进一步进行模式分解为以下形式:
选课(学号,课名,分数)
学生(学号,姓名,系名)
系(系名,系主任)
对于选课表,符合3NF的要求,之前已经分析过了。
对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,所以符合3NF的要求。
对于系表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),所以符合3NF的要求。。
新的数据表如表5
表5
现在我们来看一下,进行同样的操作,是否还存在着之前的那些问题?
删除某个系中所有的学生记录
该系的信息不会丢失。——有改进
插入一个尚无学生的新系的信息。
因为系表与学生表目前是独立的两张表,所以不影响。——有改进
数据冗余更加少了。——有改进
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。当然,在实际中,往往为了性能上或者应对扩展的需要,经常 做到2NF或者1NF,但是作为数据库设计人员,至少应该知道,3NF的要求是怎样的。
BCNF
规则: 消除候选键中的列对其它任一候选键的传递依赖或部份依赖
假定 一个教师只会教一门课程,一门课可以有很多老师教且当学生选定某门课,就对应一个固定的教师
举例:学生id 课程id 教师id
那我们可以 确定两个候选键(学生id,课程id),(学生id,教师id)
但我们发现 一个教师只会教一门课程,那么 教师id就决定了课程id 可以认为课程id依赖于教师id 那么这个表就不符合 bcnf
总结:
1NF消去对主键的部分函数依赖后=2NF。
2NF消去对主键的传递函数依赖后=3NF。
3NF消去对候选键的部分函数依赖和传递函数依赖后 = BCNF。
BCNF是对3NF的改进,即在3NF的基础的又把范围从主码扩大为候选码。