最近在看数据库设计伦理,顺便里来理清一下数据库设计的六大范式。
首先我们来了解几个概念。
- 范式(NF)
符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度。
实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别。
- “关系模式”和“关系”的区别
类似于面向对象程序设计中”类“与”对象“的区别。
”关系“是”关系模式“的一个实例,你可以把”关系”理解为一张带数据的表,而“关系模式”是这张数据表的表结构
- 码
关系中的某个属性或者某几个属性的组合,
用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)。
- 候选码
设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),
那么我们称 K 为候选码,简称为码
- 主属性
所有候选码的属性称为主属性。
不包含在任何候选码中的属性称为非主属性或非码属性。
- 函数依赖
我们可以这么理解(但并不是特别严格的定义):若在一张表中,在属性(或属性组)X的值确定的情况下,必定能确定属性Y的值,那么就可以说Y函数依赖于X,写作 X → Y。
也就是说,在数据表中,不存在任意两条记录,它们在X属性(或属性组)上的值相同,而在Y属性上的值不同。
这也就是“函数依赖”名字的由来,类似于函数关系 y = f(x),在x的值确定的情况下,y的值一定是确定的
例如,对于表3中的数据,找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作 学号 → 姓名。但是反过来,因为可能出现同名的学生,所以有可能不同的两条学生记录,它们在姓名上的值相同,但对应的学号不同,所以我们不能说学号函数依赖于姓名。
- 完全函数依赖
在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),
X ' → Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y。
例如 学号 F→ 姓名,(学号,课名) F→ 分数
- 部分函数依赖
假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,
那么我们就称 Y 部分函数依赖于 X
例如 (学号,课名) P→ 姓名
- 传递函数依赖
假如 Z 函数依赖于 Y,且 Y 函数依赖于 X ,这里改为:『Y 不包含于 X,且 X 不函数依赖于 Y』这个前提),
那么我们就称 Z 传递函数依赖于 X
第一范式(1NF)
每个字段都不可再分,也就是字段的原子性
如图违反1NF的表
但是仅仅符合1NF的设计,仍然会存在数据冗余过大,插入异常,删除异常,修改异常的问题,例如对于表3中的设计
第二范式(2NF)
在关系理论中的严格定义我这里就不多介绍了(因为涉及到的铺垫比较多),
只需要了解2NF对1NF进行了哪些改进即可。其改进是,2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。
根据2NF的定义,判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。判断的方法是:
- 第一步:找出数据表中所有的码。
- 第二步:根据第一步所得到的码,找出所有的主属性。
- 第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
- 第四步:查看是否存在非主属性对码的部分函数依赖。
第一步:
1.查看所有每一单个属性,当它的值确定了,是否剩下的所有属性值都能确定。
2.查看所有包含有两个属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。
3.……
6.查看所有包含了六个属性,也就是所有属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。
看起来很麻烦是吧,但是这里有一个诀窍,就是假如A是码,那么所有包含了A的属性组,如(A,B)、(A,C)、(A,B,C)等等,都不是码了(因为作为码的要求里有一个“完全函数依赖”)。图4表示了表中所有的函数依赖关系:
这一步完成以后,可以得到,表3的码只有一个,就是(学号、课名)。
第二步:
主属性有两个:学号 与 课名
第三步:
非主属性有四个:姓名、系名、系主任、分数
第四步:
- 对于(学号,课名) → 姓名,有 学号 → 姓名,存在非主属性 姓+ 名 对码(学号,课名)的部分函数依赖。
- 对于(学号,课名) → 系名,有 学号 → 系名,存在非主属性 系名 对码(学号,课名)的部分函数依赖。
- 对于(学号,课名) → 系主任,有 学号 → 系主任,存在非主属性 对码(学号,课名)的部分函数依赖。
所以表3存在非主属性对于码的部分函数依赖,最高只符合1NF的要求,不符合2NF的要求。
为了让表3符合2NF的要求,我们必须消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表,在拆分的过程中,要达到更高一级范式的要求,这个过程叫做”模式分解“。模式分解的方法不是唯一的,以下是其中一种方法:选课(学号,课名,分数)学生(学号,姓名,系名,系主任)我们先来判断以下,选课表与学生表,是否符合了2NF的要求?对于选课表,其码是(学号,课名),主属性是学号和课名,非主属性是分数,学号确定,并不能唯一确定分数,课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码 (学号,课名)的部分函数依赖,所以此表符合2NF的要求。对于学生表,其码是学号,主属性是学号,非主属性是姓名、系名和系主任,因为码只有一个属性,所以不可能存在非主属性对于码 的部分函数依赖,所以此表符合2NF的要求。
第三范式(3NF)
3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。
也就是说, 如果存在非主属性对于码的传递函数依赖,则不符合3NF的要求。
对于学生表,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖,所以学生表的设计,不符合3NF的要求。
为了让数据表设计达到3NF,我们必须进一步进行模式分解为以下形式:选课(学号,课名,分数)学生(学号,姓名,系名)系(系名,系主任)对于选课表,符合3NF的要求,之前已经分析过了。对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,所以符合3NF的要求。对于系表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),所以符合3NF的要求。
结论
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。当然,在实际中,往往为了性能上或者应对扩展的需要,经常 做到2NF或者1NF,但是作为数据库设计人员,至少应该知道,3NF的要求是怎样的。
BCNF范式
在 3NF 的基础上消除主属性对于码的部分与传递函数依赖。
第四范式(4NF)
满足3NF,消除表中的多值依赖
理解:显然一个关系模式是4NF,则必为BCNF。也就是说,当一个表中的非主属性互相独立时(3NF),这些非主属性不应该有多值,若有多值就违反了4NF
第五范式 (5NF)
第五范式有以下要求:(1)必须满足第四范式;(2)表必须可以分解为较小的表,除非那些表在逻辑上拥有与原始表相同的主键。
第五范式是在第四范式的基础上做的进一步规范化。第四范式处理的是相互独立的多值情况,而第五范式则处理相互依赖的多值情况。