读书的时候,上数据库课,一开头就讲范式。一直搞不懂这个范式,也不清楚是做什么的。
其实,范式跟我们数据库的表设计息息相关。或者这么说,其实我们许多时候基本都懂得怎么设计表,实际上已经运用了范式的知识但不一定知道。
比如,我们开发一个课程管理系统,有学生,有课程,需要弄一张表来记录学生选修课程的情况,套路必定是一张学生表(学号,姓名),课程表(课程号,课程名),选修表(学号,课程号),一般不会将选修表设计成(学号,课程号,姓名,课程名)。为啥呢,因为我们很清楚,如果像后面这样设计的话,会有许多冗余数据,插入、更改,都很麻烦。其实,范式说的就是这些东东。
范式属于关系数据库里面规范化理论的范畴。从低到高,依次为 1NF(第一范式)、2NF、3NF、BCNF(Boyce Codd Normal Form,巴斯范式/鲍依斯-科得范式,由Boyce和Codd提出,比3NF又进了一步,通常认为是修正的第三范式)。范式越低,越容易出现数据冗余、插入、删除异常等问题。但范式越高,关系模式(即关系型数据库中的表)的粒度就越细,要获取完整数据需要连接的表就越多,有时候为了提高性能,又需要增加冗余列、派生列,水平或垂直分割表,甚至重新组表,反规范化,人为降低范式。
要了解范式,先弄清楚一些基本的概念。
候选键(候选码):
唯一标识元组,且无冗余。其实就是可以做主键的字段,元组就是行。一个表可能有多个字段都能做主键,它们就称为候选键,候选的。
主键
任选一个候选键作为主键。当然如果只有一个就不用选了。
主属性
候选键里的属性。属性就是字段。如果候选键有多个属性(主键是复合字段),那么这些属性都是主属性。
非主属性
不在候选键里的属性。
依赖
*设R(U)是一个属性集U上的关系模式,X和Y是U的子集。
若对于R(U)的任意两个可能的关系r1、r2,若r1[x]=r2[x],则r1[y]=r2[y],或者若r1[y]不等于r2[y],则r1[x]不等于r2[x],称X决定Y,或者Y依赖X。*记作: x -> y
比如在设计学生表时,一个学生的学号能决定学生的姓名,也可以说姓名依赖于学号。
部分依赖
如果存在依赖集 {AB->C,A ->C },则称C部分函数依赖于AB。也可以理解为,C完全依赖于A,只是依赖于AB的部分。
比如,关系模式R(学号,课程号,课程名,成绩)里,候选键是(学号,课程号) 。显然,这个候选键可以决定课程名;而(课程号)同样也可以决定课程名,那么就说在R(学号,课程号,课程名)里,{课程名}部分函数依赖于候选键(学号,课程号)。
传递依赖
如果 A -> B,B -> C,则有A -> C,此为传递函数依赖。
比如: 在关系R(学号,宿舍,费用)中,通过{学号}可以得到{宿舍},通过{宿舍}可以得到{费用},而反之都不成立,则存在传递依赖{学号}->{费用}。
所谓依赖,包括部分依赖,传递依赖,都是指依赖候选键。可以是非主属性依赖候选键,也可以是主属性依赖候选键。2、3、BN范式,都跟依赖有关。
有了上面的知识铺垫,就比较容易理解数据库范式了。
1NF
属性值都是不可分的原子值
原子值,什么意思?就是不可再分的意思。就是字段值只代表一样东西,不能再细分。相反的例子,有
产品表(编号,品名,进货,销售,备注),其中进货包含了数量和单价两个信息,销售也类似,这就不是原子值。一般我们设计表都不会这样干,也就是说,我们设计的表,肯定符合第一范式。不过,凡事也有例外,有时不想增加字段,就将一些信息全部塞到一个字段里,用逗号隔开之类,那这就不符合第一范式了。连第一范式都不符合,遑论第2范式或者更高。
2NF
消除非主属性对候选键的部分依赖(换言之,完全依赖可以,部分依赖不行)
选修课程(学号,课程号,成绩,学分)。这张表就不符合第二范式。因为候选键是(学号,课程号),而学分仅由课程号决定,因此{学分}部分依赖于候选键(与此形成对比的是,{成绩}完全依赖于候选键)。
因此拆成2张表
选修课程(学号,课程号,成绩)
课程(课程号,学分)
这样就肯定符合第二范式了。
3NF
消除非主属性对候选键的传递依赖
如前所述,所谓依赖,包括部分依赖,传递依赖,都是指依赖候选键。这张学生信息表,候选键是学号。由于表中字段都不可再分,因此符合第一范式;在此基础上,由于候选键只有一个属性,因此不可能存在部分函数依赖,因此肯定符合第二范式。但由于系名依赖于系号,而系号又由学号决定,因此,系名间接由学号决定,因此不符合第三范式。
怎么搞呢,不必多言,拆表咯。
BCNF
消除主属性对候选键的部分和传递依赖。
也就是说,一张表里,只要没有了部分函数依赖和传递依赖,它就符合BC范式。
平心而论,日常工作中,我们设计的表,基本都符合第三范式,规规矩矩,没啥冗余数据。但是这个BCNF比第三方式高级在哪里呢?我感觉第三范式已经很好很足够了。
举一个符合第三范式但又不符合BC范式的例子:
选课表(学生号,老师号,课程号)。
通常,一个教师只教一门课,而每门课会有若干教师(这很好理解,比如大家都是高数老师,有的教这些系,有的教那些系,分工不同)。么某一学生选定某门课,就对应一个固定的教师。那么有函数依赖如下:
(学生号,老师号)→课程号;(学生号,课程号)→老师号;老师号→课程号
如前所述,依赖都是对候选键而言,此表中,候选键有2个:(学生号,课程号)、(学生号,老师号)。在我们这个表中,只能有复合候选键,单个属性是做不了主键的。很明显,学生号、课程号、老师号都是主属性(由定义可知),因此这个表里没有非主属性,所以肯定符合第三范式了。但从上面的函数依赖可以看出,传递依赖很明显,所以它不符合BCNF。
既然老师号可以决定课程号,为什么还要把课程号放到表里呢?可以通过老师表去找到课程号,然后再找到课程信息啊。但是,这样不觉得很绕吗?看来,达到第三范式应该就OK了,犯不着追求更高的范式。
BCNF也称为修正的第三范式,或曰第三范式的威力加强版。更高的还有第四、第五范式,那应该跟我们日常工作关系不大。