规范化理论
1.数据依赖
数据依赖是一个关系内部属性和属性之间的一种约束。这种约束关系是通过属性间值的相等与否体现出来的一种关联关系,是现实语义的体现。其类型包括函数依赖(Function Dependency,FD)和多值依赖(Multi-Valued Dependency,MVD)。
什么是函数依赖?
比如描述一个学生关系,可以有学号(Sno)、姓名(Sname)、系别(Sdept)等属性。由于一个学号只对应一个学生,一个学生只对应一个系,因此,学号确定后,则对应的姓名和系别也唯一的确定了。可记作:Sno→Sname,Sno→Sdept。即属性X确定后,属性Y也唯一的确定了(称为X确定Y,或Y依赖X),用数学语言描述,即存在函数关系f,y=f(x)。或者,换句话说,不存在两个元组在属性X上的值相等而在属性Y的值不等,这就是函数依赖。
示例:有这么一个学校教务的数据库,涉及学生的学号(Sno)、所在系别(Sdept)、系主任名字(Mname)、课程号(Cno)和成绩(Grade)。根据现实语义,可以有这样的函数依赖关系:
F = {Sno→Sdept,Sdept→Mname,(Sno, Cno)→Grade}
完全函数依赖?
(Sno, Cno)→Grade 就是完全函数依赖,因为属性Grade完全函数依赖于Sno和Cno,少一个属性都不行。
部分函数依赖?
(Sno, Cno)→Sdept 就是部分函数依赖,因为对于属性Sdept,由单个属性Sno就已经可以确定了,即Sno→Sdept,而Sno是(Sno, Cno)子集。
传递函数依赖?
简单的讲,因为Sno→Sdept,且Sdept→Mname,所以可以得出Sno→Mname,这就是传递函数依赖。(这样描述其实不够严谨,但不妨碍理解)
什么是候选码/主码/码?
关系中的某个属性或者某几个属性的组合,用于区分每个元组,称为候选码。所谓“区分元组”,就是指当一个元组的候选码确定了,则元组中的其他属性也就确定了,且不同元组的候选码一定是不同的!如果一个元组有多个候选码,则一般选取其中一个作为主码。在使用中,常常把候选码和主码都简称为码。此外,包含在码中的属性称为主属性,不包含在码中的属性称为非主属性。
2.范式(Normal Form,NF)
范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。我们可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别。就像家里装修买建材,最环保的是E0级,其次是E1级,还有E2级等等。数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计关系型数据库的时候,最多考虑到BCNF就够。符合高一级范式的设计,必定符合低一级范式,例如符合2NF的关系模式,必定符合1NF。
1NF:在一个关系中,属性不可再分。即不允许表中有表。
第一范式(1NF)是所有关系型数据库的最基本要求,但仅仅符合1NF的设计,很可能会出现数据冗余、更新复杂、插入异常、删除异常的问题。例如下面这张表:
存在如下几个问题:
- 学号、姓名、系名、系主任等信息重复多次,出现的次数与(学号,课名)出现的次数一致——数据冗余。
- 由于数据冗余,当需要更新数据时,代价昂贵。比如,现在某系的系主任换了,则与之有关的所有记录都得更新——更新复杂。
- 假如一个系刚成立(系名、系主任的信息有了),还没有招生(学号、姓名的信息没有),则无法插入数据——插入异常。
- 根据码的概念,该表中,码应该为(学号,课名),根据实体完整性约束,插入一条新的记录时主码不能为空,所以不能插入
- 假如将某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(假如一个系所有学生都毕业了,并不表示这个系就没有了)——删除异常。
2NF:在满足1NF的基础上,消除了非主属性对候选码的部分函数依赖,即要求每个非主属性完全函数依赖于候选码。
以上表为例,码是(学号,课名),即主属性是学号和课名,非主属性是姓名、系名、系主任和分数。那么任意一个非主属性对码是完全函数依赖还是部分函数依赖呢?很显然,属性 姓名 对码 (学号,课名) 就是部分函数依赖,因此,该表不符合2NF的要求。为了让上表符合2NF的要求,我们必须消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表,在拆分的过程中,要达到更高一级范式的要求,这个过程叫做“模式分解”。模式分解的方法不是唯一的,以下是其中一种方法:
- 选课(学号,课名,分数)
- 学生(学号,姓名,系名,系主任)
我们先来判断以下,选课表与学生表,是否符合了2NF的要求?
对于选课表,其码是(学号,课名),主属性是学号和课名,非主属性是分数,学号确定,并不能唯一确定分数,课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码 (学号,课名)的部分函数依赖,所以此表符合2NF的要求。
对于学生表,其码是学号,主属性是学号,非主属性是姓名、系名和系主任,因为码只有一个属性,所以不可能存在非主属性对于码的部分函数依赖,所以此表符合2NF的要求。
分解后的表如下:
那么,对于1NF中的表,该表是否有改进呢?(与前面的一一对应)
- 姓名、系别、系主任的冗余程度明显减少了。(有改进)
- 由于冗余程度降低,那么更改的复杂度也降低了。(有改进)
- 若存在刚成立但还未招生的系,仍然无法插入数据。(无改进)
- 若删除一个系的所有学生,那么该系的其他信息(系名、系主任)也被删除了。(无改进)
3NF:在满足2NF的基础上,消除了非主属性对于候选码的传递函数依赖。也就是说, 如果存在非主属性对于码的传递函数依赖,则不符合3NF的要求。
对于2NF中的表,我们根据3NF的要求,分析如下:
对于选课表,主码为(学号,课名),主属性为学号和课名,非主属性只有一个,为分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。
对于学生表,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号→系名,且有系名→系主任,所以存在非主属性(系主任)对于码(学号)的传递函数依赖,所以学生表的设计,不符合3NF的要求。
为了让数据表设计达到3NF,我们必须进一步进行模式分解为以下形式:
- 选课(学号,课名,分数)
- 学生(学号,姓名,系名)
- 系(系名,系主任)
对于选课表,符合3NF的要求,之前已经分析过了。
对于学生表,码为学号,主属性为学号,非主属性为姓名和系名,不可能存在“学号→姓名 & 姓名→系名(因为同姓名的人是有很多哒)”,或是“学号→系名 & 系名→姓名”,所以符合3NF的要求。
对于系表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),所以符合3NF的要求。。
经过改进后,符合3NF要求的数据库设计,基本解决了数据冗余、更新复杂、插入异常、删除异常的不足。
BCNF:消除主属性对码的部分函数依赖和传递函数依赖。 (BCNF和4NF暂且不探究了,太难了。。。)
3.总结
在关系数据库中,最基本的要求是满足第一范式。但是,在一些表结构的设计中,会发现存在数据冗余、插入异常、删除异常和修改复杂的问题,于是就要解决这些问题,这就是规范化的目的。从1NF到2NF,3NF...的演进,概括的说,就是把大表逐步拆分为小表的过程,逐步消除数据依赖中不合适的部分。所谓规范化的实质,就是概念的单一化。