什么是数据库范式
在设计关系数据库的时候,要求遵循不同的规范,而这些规范就被称为范式。
有如下的几种范式:
第一范式(1NF):在关系数据库中,每张表的属性应该具有原子性。
第二范式(2NF):在1NF基础上消除非主属性对主码的部分函数依赖,非码属性必须完全依赖于候选码。
第三范式(3NF):在2NF基础上消除传递依赖,任何非主属性不依赖于其它非主属性。
巴斯-科德范式(BCNF):在3NF基础上消除对主码子集的依赖,任何非主属性不能对主键子集依赖。
第四范式 (4NF):在BCNF基础上消去其中不是函数依赖的非平多值依赖。
第五范式(5NF):在4NF基础上,表必须可以分解为较小的表,除非那些表在逻辑上拥有与原始表相同的主键。
可以看出范式是呈现递次规范的,而越高的规范数据库的冗余就越小。
在解释范式之前,有必要先了解一些概念:
候选码/候选键(候选关键字)、主码/主键(主关键字)、主属性、非主属性。
在后面统一用码来描述,首先一张表肯定要存在有码这种关系,码在最上面已经给出了解释。
**码/超键:**数据表中关系的某个属性或者某几个属性的组合,用于区分每个元组。
**元组:**表中的每行记录。
码、候选码、主码三者关系:
图:
候选码:唯一标识一条记录的最小属性集。
- 标识性:一个数据表的所有记录都具有不同的候选键
- 最小性:候选键的任何子集都不能唯一标识一个记录
- 非空性:不能为空
- 候选键是没有多余属性的超键。
例如学号是候选码,包含有候选码的属性集都是码。
主码:某个能够唯一标识一条记录的最小属性集(从候选码挑选的一条)。
- 唯一性:一个数据表只能有一个主键
- 标识性:一个数据表的所有记录都具有不同的主键取值
- 非空性:不能为空
- 人为的选取某个候选码为主码
**外键:**子数据表中出现的父数据表的主键。
例如:
班主任也有编号,在教师表中,但是班主任编号在学生表中就是属于外键。
主属性:候选码属性的并集。
非主属性:相对于主属性来说,不包含在候选码中的属性。
函数依赖:
例如:在学生信息表中,不允许重名,如果重名就不是函数依赖关系了。
在这张表中学号就代表了学生,如果有两个名字相同的,例如两个小明,那么1001就不能代表小明这一属性了,也就是说姓名不能够依赖于学号这一属性了。如果不允许重名,那么就可以说学生姓名函数依赖于学号,记作学号—>姓名。
表中还有这些依赖关系:
学号—>性别
学号—>年龄
姓名—>性别
姓名—>年龄
如果表中多了一个属性课名,则学号—>课名就不成立了,因为指不定一个人有很多的课程。
完全函数依赖:
在上面表中加入班级属性。
假设不同的班级学号有相同的,班级内学号不相同,在表中(学号、班级)—> 姓名 ,
但是学号—>姓名,班级—>姓名 不成立,所以在这张表中姓名完全函数依赖于(学号、班级)。
部分依赖关系:
假设表中学号属性值是唯一的,在表关系中存在:(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名),则可以说姓名部分函数依赖于(学号,身份证号)。
传递函数依赖:
在表关系中,姓名函数依赖于宿舍,但是反过来就不行,因为指不定宿舍不止一个人,同样宿舍函数依赖于费用,反过来不行,因为不知道每个人的费用情况为多少,而费用又函数依赖于姓名,所以在这个表关系中费用传递函数依赖于姓名。
最小函数依赖:
假设不同的班级学号有相同的,班级内学号不相同,则姓名完全函数依赖于(学号、班级)记为 (学号、班级)—> 姓名,而姓名为单属性的,此时就的(学号、班级)—> 姓名中间没有传递函数依赖层,所以可以说(学号、班级)—> 姓名就为表关系的最小函数依赖。
例如 班级—>班长,班长—>班主任,班级—>班主任,其中班级—>班主任在表关系中就不是最小函数依赖了,因为班级—>班长,班长—>班主任,就代表着班级—>班主任。
而这些最小函数依赖组成的集合就是最小函数依赖集。
平凡函数依赖:
假设不同的班级学号有相同的,班级内学号不相同,在表关系中存在(学号,班级,姓名)函数依赖于姓名,而姓名属性也包含在(学号,班级,姓名)这个属性集里面,此时就能说上一组依赖为平凡函数依赖。
非平凡函数依赖:
假设不同的班级学号有相同的,班级内学号不相同,在表关系中存在(学号,姓名,班级)—>(班长,班主任),此时的(学号,姓名,班级)这个集不包含(班长,班主任)这个属性集,则可以称函数依赖为非平凡函数依赖。
1NF
根据第一范式的关系模型要求,数据表的每一列都具有原子性,也就是每一列都不可分隔出来。
像这样就不行,这样不符合关系数据库的设计要求:
首先来看看仅仅是符合第一范式的数据表所呈现的问题:
虽然只是简单的满足了1NF,但是还是会存在数据冗余过大,插入、删除、修改异常的问题。
表中每个字段数据都出现了多次的重复,例如小王与大王关联任务数据出现重复,数据的冗余过大。
问题:
插入异常
如果当人们有了夜宵的习惯,那么数据库表就要加入该字段,而目前的小王与大王都没有夜宵的习惯,那么该表就无法单独为夜宵这一任务进行添加了。
问题解决:
受表中关系完整性的约束,每个关系组合都不能为空,也就是码不能为空,且属性的组合不能重复,
所以只能将实体小王与大王和其他属性组合分开即:
删除异常
在上面的1NF问题图中如果单单的删除一个实体对象的整行记录(1)的话,那么与之组合的其他属性就会随之消失,这样其他的人例如大王就没有早晨吃了,就算小王大王不吃早餐,如果其他人要吃,准备进来怎么办,发现没有早餐吃,不就异常了。
修改异常
如果小王比较特殊,经常熬夜通宵,夜晚正是他最精神的时刻,晚上18:30才吃早餐,所以他把数据改了一下,但是数据库中数据是具有一致性的,这时候其他人/大王不干了,本来晚餐应该大吃一顿的,却让我只能吃面包油条,罢工。
2NF
那么2NF是怎么解决这样的问题的呢?
在1NF基础上消除非主属性对主码的部分函数依赖,非码属性必须完全依赖于候选码。
为了能够更严谨的解决问题,后面将改为另一张比较经典的表。
如何判断表中是否符合2NF呢?
首先找出该表的所有码:(学号、课名),所以主属性为学号与课名,自然非主属性为姓名,系名,系主任,分数了。而又该表中存在非主属性对码的部分函数依赖,例如:学号—>姓名等等。所以很明显是不符合2NF的。
怎么改进呢?就行分解,这里这样修改:
选课表:(学号,课名,分数)
学生表:(学号,姓名,系名,系主任)
再来看看两张表的关系:
选课表中的码:(学号,课名),所以主属性为学号、课名,非主属性为分数。
该表中如果学号确定,并不能唯一确定分数,课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码**(学号,课名)**的部分函数依赖,所以此表符合2NF的要求。
学生表中的码:学号,所以主属性为学号,非主属性为姓名、系名、系主任,因为码只有一个属性,所以不会存在非主属性对码的部分函数依赖,所以该表也符合2NF的要求。
来看看两张表中是否存在1NF中的删除,插入,修改异常。
删除:
如果删除一个系的话,则系的全部信息会丢失了。
插入:
还是不能直接插入一个系。
修改:
如果小明转系了,直接修改即可。
可以发现,将1NF中的问题表分解成两个表,解决了属性值修改异常的问题,也的确减少了数据冗余情况,但是还是存在删除与插入的异常。
而3NF又是怎么去解决这个问题呢?
3NF
在2NF的基础之上,消除了非主属性对码的传递函数依赖,如果存在该关系,则不符合3NF。
回到2NF中的两个表:
选课表:(学号,课名,分数)
学生表:(学号,姓名,系名,系主任)
选课表:由于非主属性只有一个,所以不可能存在传递函数依赖,所以符合。
学生表:存在学号—>系名,系名—>系主任,学号—>系主任,存在传递函数依赖,所以不符合。
怎么解决呢?对出现问题的学生表就行分解:
分解成:
学生表:(学号,姓名,系名)
系表:(系名,系主任)
分析这两个表:
学生表:非主属性只有系名一个,不会存在传递函数依赖,所以符合。
系表:非主属性只有一个,同样不符合,且表中只有两个属性,至少三个或者以上才可能会出现传递函数依赖。
再来看看2NF中留下的问题能不能解决?
删除:删除某个系的学生,系信息不会丢失,成功。
插入:直接插入,无影响,成功。
在平常的数据库表设计中,必须懂得第一二三范式,不然表数据冗余过大就不好了。
而实际的设计中,又非得必须遵守这三条铁律,依情况而定。