一般而言,关系数据库的设计目标是生成一组关系模式,使我们存储信息时避免不必要的冗余,并且让我们可以方便地获取信息。这是通过设计满足适当范式(normal form)的模式来实现的。
引例
-
首先有如下两个模式
- instructor( <u>ID</u>, name, dept_name, salary )
- department( <u>dept_name</u>, building, budget )
-
若将上面两个模式合并成一个大的模式
inst_dept(ID, name, salary, dept_name, building, budget)
存在数据冗余(building和budget被存储了多份,实际上每个系的楼和预算只要存一次就好了)
存在插入、删除和更新异常
-
需要分解成更小的模式
-
并不是所有模式的分解都是有益的
employee(ID, name, street, city, salary)
-
分解成下面两个模式
- employee1(ID, name)
- employee2(name, street, city, salary)
-
由于可能存在同名的可能,所以分解以后导致了数据的丢失
- 比如有两个叫Sunny的人,我们用其中一个Sunny的ID去查询他的地址信息的时候,因为存在两份信息相互混淆,导致得不到正确的结果,但是在分解之前通过ID是可以得到唯一的地址的
-
有损分解和无损分解
- 像上面的例子中,分解以后导致信息丢失的分解称之为有损分解(在实际进行分解时要避免这种分解)
- 反之则称之为无损分解(无损分解在重新合并后可以得到和分解之前一致的状态)
函数依赖
-
什么是函数依赖?
- 有如下模式
- U = {Sno, Sname, Cno, Sdept, Mname, Grade} (分别代表学号、学生姓名、课程号、系、系主任、分数)
- Sname = f(Sno) ==>学生的姓名函数依赖于学号
- 记作:Sno→Sname
- 读作:Sno推出Sname
- 有如下模式
-
非平凡函数依赖和平凡函数依赖
- X 和 Y 为模式U中一个或多个属性的集合
- 非平凡函数依赖
- X→Y,但Y∉X
- 平凡函数依赖(包含冗余)
- X→Y,且Y∈X
-
完全函数依赖和部分函数依赖
写函数依赖时,完全函数依赖和部分函数依赖的写法中,P和F是写在箭头的正上方的,由于Markdown不能很好的展现,就标在了箭头的右上角
所以用下面的表示法:- →F :完全依赖
- →P :部分依赖
- 完全函数依赖
- 在模式R(U)中X→Y,但是对于任意X的真子集X’, 都没有X‘→Y, 则称Y完全函数依赖于X,记作X→FY
- X实际上就是能推出Y的最小集
- 例如:(Sno, Cno)→Grade
- 部分函数依赖
- 在模式R(U)中X→Y,存在某个X的真子集X’, 有X‘→Y, 则称Y部分函数依赖于X,记作X→PY
- 例如:(Sno, Cno)→Sdept
- 而实际上:Sno→Sdept 也是可行的,上面的依赖关系中,Cno是冗余的
-
传递函数依赖
- X→Y, Y→Z ==> X→Z
码
-
超码、候选码和主码
- 可以沿用SQL中码的概念
- 超码是可以唯一标识一个元组的属性集(可以有多个)
- 候选码是每个超码中去除不必要属性但是仍然能够标识一个元素的最精简属性集(可以有多个)
- 主码只有一个,在候选码中选一个当做主码
-
用函数依赖来定义
- K为R< U, F >中的属性或属性组合,若K→U(→上面有一个F)(即U完全依赖于K),则称K为R的候选码,若候选码多与一个,则选择其中一个作为主码
-
主属性与非主属性
- 主属性:包含于某个候选码的属性
- 非主属性:不被任何候选码包含的属性
范式
-
首先还是构造一个例子
R< U, F>
U = {Sno, Sdept, Sloc, Cno, Grade} (分别代表学号、系、系所在楼、课程号、分数)
F = {(Sno, Cno)→F Grade, Sno→Sdept, (Sno, Cno)→P Sdept, Sno→Sloc, (Sno, Cno)→PSloc}
主属性:Sno, Cno
非主属性:Sdept, Sloc, Grade
-
上面的依赖关系可以用下面的图示表示
-
第一范式(1NF)
-
一个域是原子的(atomic),则该域的元素被认为是不可分的单元
- 比如name属性就可以不是原子的,name(first_name, middle_name, last_name)
一个关系模式R属于第一范式(1NF)==>R上的所有属性都是原子的
上述例子中的每个属性都是原子的,所以满足第一范式
-
-
第二范式(2NF)
-
R ∈ 1NF,并且每一个非主属性完全依赖于码(实际上就是在1NF的基础上,去除了非主属性对码的部分函数依赖)
上述例子中存在非主属性对码的部分函数依赖,故不满足2NF
-
对上述例子作出如下分解
R1< {Sno, Cno, Grade}, {(Sno, Cno)→FGrade} >
-
R2< {Sno, Sdept, Sloc}, {Sno→Sdept, Sdept→Sloc}>
- ps:其中R2隐含了Sno→Sloc (由之后即将学习的AmStrong公理可以推算出来)
-
上面的依赖关系可以用下面的图示表示
经过上面的分解之后,每一个非主属性都完全依赖于码了,所以分解之后的模式满足2NF
-
-
第三范式(3NF)
-
R∈2NF, 并且不存在非主属性对码的传递依赖(实际上就是在2NF的基础上去除了非主属性对码的传递依赖)
-
上述经过分解以后的模式中存在非主属性对码的传递依赖,故不满足3NF
- Sno→Sdept, Sdept→Sloc ==> Sno→Sloc
-
对上面的模式进行进一步的分解
R1< {Sno, Cno, Grade}, {(Sno, Cno)→FGrade} >
R2< {Sno, Sdept}, {Sno→Sdept} >
R3< {Sdept, Sloc}, {Sdept→Sloc} >
-
上面的依赖关系可以用下面的图示表示
- 经过上面的进一步分解之后,就不存在非主属性对码的传递依赖了,故进一步分解之后的模式满足3NF
-
2NF和3NF中的约束都是针对于非主属性的。在日常开发的时候只要ER图设计好,一般都是满足到3NF的,3NF也是可以保证无损分解的最高范式
-
Boyce-Codd范式(BCNF)
-
R ∈ 3NF, 并且不存在主属性间的部分函数依赖和传递函数依赖 (实际上就是在3NF的基础上,去除了主属性的部分函数依赖和传递函数依赖)
-
举个栗子
R< U, F >
U = {S, T, J}
F = { (S, J)→T, (S, T)→J, T→J }
-
从上面的函数依赖易判断出:S、T、J都是主属性
因为{S, J}, {S, T} 都是候选码
因为不存在非主属性,所以该模式已经满足到3NF
-
上面模式的依赖可以用下面的图示表示
上面的栗子中的模式主属性中存在部分函数依赖,所以不满足BCNF
-
作出如下分解
R1< {S, T, J}, { (S, J)→T, (S, T)→J } >
R2< {T, J}, { T→J } >
-
上面的依赖关系可以用下图表示
经过分解之后的模式就不存在主属性间的部分函数依赖和传递函数依赖, 故满足BCNF
当我们分解不属于BCNF的模式的时候,产生的模式中可能有一个或多个不属于BCNF。在这种情况中,需要进一步分解,其最终结果是一个BCNF的模式集合
-
更高的范式还有第四范式和第五范式,但是应用较少,且考纲不要求,这里就不再赘述了