关系型数据库设计时为确保数据存储规范化,通常需要按照范式设计数据,接下来主要介绍下1NF-3NF递进式数据库设计,4NF、5NF日常使用较少不包含在本次内容中。
首先总体描述下1NF-3NF转化的过程
1NF------------------>2NF 消除非主属性对码的部分函数依赖
2NF------------------>3NF 消除非主属性对码的传递函数依赖
1NF:
1NF是数据库最基本的要求,每个属性不可分割。但如下图可以看到如果仅仅满足1NF会有大量数据冗余,造成数据存储占用大量空间,同时存在插入、修改、删除异常问题。
插入异常:新建一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),无法将系名与系主任的数据单独地添加到数据表中去的
更新异常:假如李小明转系到法律系,为了保证数据库中数据的一致性,需要修改三条记录中系与系主任的数据
删除异常:某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)
2NF:
2NF在1NF基础上消除非主属性对码的部分函数依赖,这句话涉及4个概念:“码”、“非主属性”、“部分函数依赖”和“函数依赖”,下面对这些概念进行解读。
码:假设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),那么我们称 K 为候选码,简称为码,即当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码,一个表可以有多个码。
如上图1NF中,码是(学号,课号)。通过学号可以确认姓名、院系、主任名称。学号和课名可以确定成绩。
非主属性:除去所有的主属性,剩下的就都是非主属性
函数依赖:在一张表中如果属性x的值确定,那么属性y的值也就确定下来了,可以说属性y函数依赖x,即x——>y,y=f(x)。比如:上图中一个学号唯一对应一个姓名,则可以说姓名依赖与学号,但反过来就不对,因为姓名有重名的可能,这样一个姓名可能对应多个学号,即学号——>姓名。还有学号——>系号,系号——>系主任。
对函数依赖进行细分后可以划分为三大类,分别是:
安全函数依赖:在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X ' → Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y。比如:(学号,课名) F→ 分数 ,通过学号无法确定分数,通过课名也无法确定分数,只有两者作为一个整体才可以确定分数。
传递函数依赖:假如 Z 函数依赖于 Y,且 Y 函数依赖于 X (严格来说还有一个X 不包含于Y,且 Y 不函数依赖于Z的前提条件),那么我们就称 Z 传递函数依赖于 X ,记作 X T→ Z
部分函数依赖:假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X,记作 X P→ Y。比如:(学号,课名) P→ 姓名,但姓名也依赖于学号。
数据表结构是否符合2NF的判断方法是:
1、找出数据表中的码。
2、根据码确定主属性。
3、确定非主属性。
4、判断非主属性是否部分依赖于码。
1NF数据梳理的函数依赖关系如下:
对1NF数据应用2NF判断方法确定是否上述表结构是否属于2NF。步骤如下:
第一步:表中对码是(学号、课名)
第二步:主属性是学号、课名
第三步:非主属性是分数、姓名、系名、系主任
第四步:
(学号、课名)——>分数,存在非主属性分数对(学号、课名)的完全依赖,因为只有两者一起才可以确定分数值。
(学号、课名)——>姓名,存在非主属性姓名对(学号、课名)的部分函数依赖,因为学号——>姓名
(学号、课名)——>系名,存在非主属性姓名对(学号、课名)的部分函数依赖,因为学号——>系名。
所以上述数据表结构只属于1NF而不是2NF。那如何将上述表转变为2NF?
按照1NF到2NF的转化过程,需要做的是消除部分函数依赖,那就需要将上面数据表结构拆分成多个数据表。如下拆分方法:
选课(学号,课名,分数)
学生(学号,姓名,系名,系主任)
接下来应用是否为2NF判断方法:
对于选课(学号,课名,分数)表,码是学号、课程,非主属性是分数,分数列完全函数依赖(学号、课名)。
而学生(学号,姓名,系名,系主任)表,码是学号,非主属性是姓名、系名、系主任,因为码只有一个主属性,姓名、系名、系主任完全函数依赖学号。符合2NF规范。
数据拆分后新的函数依赖关系:
验证是否还存在插入、修改、删除异常问题。
测试:
1、李小明转系到法律系
只需要修改一次李小明对应的系的值即可。——有改进
2、数据冗余是否减少了?
学生的姓名、系名与系主任,不再像之前一样重复那么多次了。——有改进
3、删除某个系中所有的学生记录
该系的信息仍然全部丢失。——无改进
4、插入一个尚无学生的新系的信息
因为学生表的码是学号,不能为空,所以此操作不被允许。——无改进
如上可知符合2NF的要求,很多情况下还是不够的,而出现问题的原因,在于仍然存在非主属性系主任对于码学号的传递函数依赖。为了能进一步解决这些问题,还需要将符合2NF要求的数据表改进为符合3NF的要求。
3NF:
在2NF的基础之上,消除了非主属性对于码的传递函数依赖。存在非主属性对于码的传递函数依赖,则不符合3NF的要求。
依据要求判断是否满足3NF。
选课(学号,课名,分数)表,码是(学号、课名),分数完全依赖(学号、课名)。
学生(学号,姓名,系名,系主任)表,码是学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递。不符合3NF要求。
将2NF中的表进行下一步分解后如下:
选课(学号,课名,分数)
学生(学号,姓名,系名)
系(系名,系主任)
选课表属于完全函数依赖,满足3NF。
学生表码是学号,非主属性是姓名、系名,属于完全函数依赖,满足3NF。
系表,码为系名,主属性为系名,非主属性为系主任,属于完全函数依赖,满足3NF。
验证是否存在之前插入、修改、删除异常问题。
1、删除某个系中所有的学生记录
该系的信息不会丢失。——有改进
2、插入一个尚无学生的新系的信息。
因为系表与学生表目前是独立的两张表,所以不影响。——有改进
3、数据冗余更加少了。——有改进
结论
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。当然,在实际中,往往为了性能上或者应对扩展的需要,经常做到2NF或者1NF。