这篇笔记讲解关系模式设计范式。范式,就是一种设计规范,一种设计要求。我们设计的关系模式符合规范,就能保证设计优雅,且插入删除不出bug。
关系模式的范式主要有4种:
上一节我们介绍了一些数据操作中可能发生的问题,满足上述范式条件的关系模式,就可以在不同程度上避免“冗余问题、插入问题、更新问题和删除问题”。
这5种范式,每一种都是建立在上一种范式上的更高要求:即 符合1NF的关系模式⊃符合2NF的关系模式⊃符合3NF的关系模式⊃符合BCNF的关系模式⊃符合4NF的关系模式。
某一关系模式R为第n范式,可简记为R∈nNF。
设R是一个关系模式。如果R的每个属性的值域都是不可分的简单数据项的集合,则称这个关系模式为第一范式关系模式,记作1NF。
1NF很好理解,想举一个反例都比较难。我们以后讨论的关系模式,都是默认符合1NF的。
若关系模式R是1NF,而且每一个非键属性都完全函数依赖于R的候选键,则R称为第二范式关系模式,记作2NF。这里可以翻回上一节看看完全函数依赖的概念。
这个定义说的其实比较难理解,我们不用管它。直接看一个例子来理解2NF:
考虑关系模式R
R={student_id, course_id, course_name, teacher, student_name}
我们想表达的是学生和选课课程的关系,但是显然一个学生可能选了不止一个课程,这个关系模式就无法满足我们的需求,我们必须把它拆开。
R1={student_id, student_name, course_id}
R2={course_id, course_name, teacher}
总结来说,2NF表达的就是一个关系只做一件事。实际上,即使不学设计范式,我们凭经验也会把课程关系和学生关系分开,不可能设计出违反2NF的数据表的,因为像上面例子那样的数据表,根本没法插入啊,设计上就是错的。
如果关系模式R是2NF, 而且它的任何一个非键属性都不传递地依赖于任何候选键,则R称为第三范式关系模式,记作3NF。通俗的说,就是每个非主键属性都和主键有直接关系,而不是间接关系。
举个例子
R={student_id, student_name, department_id, department_name}
我们表达学生信息,其中包括了该学生的专业信息。其中有传递依赖department_name->department_id->student_id
,如果我们要插入一个新的专业,但是这个专业当前还没有学生,我们就没法表达这个插入操作了。
有同学可能不耐烦了,这不废话么,设计表时怎么可能是这么设计的,从面向对象软件开发的角度,必然是有一个专业实体类,有学生实体类,那对应到数据表也是一样的,肯定是分开的。其实三个范式都是这样的,我们凭经验基本不会设计出违反这三个范式的数据表,因为我们要保证软件运行结果是对的,而且符合面向对象,其结果就是随便设计的数据表其实都是满足三个范式的。那学这些还有什么用呢?其实就是从抽象的角度学习关系数据库理论,就像假设我们是外星人,学习地球人发明的加减乘除,可能要从抽象代数学起一样,这是站在更高的角度来看问题。
设关系模式R是1NF。如果对于R的每个函数依赖X→Y,X必为候选键,则R是BCNF范式。
BCNF的关系模式所具有的性质:
如果一个关系数据库中的所有关系模式都属于BCNF,那么在函数依赖范畴内,它已实现了模式的彻底分解,达到了最高的规范化程度,消除了插入异常和删除异常。
关系模式R∈1NF,如果对于R的每个非平凡多值依赖X→→Y(Y⊈X),X都含有候选码,则R∈4NF。
多值依赖的概念:设R(U)是属性集U上的一个关系模式,X、Y和Z是U的子集,并且Z=U-X-Y
,多值依赖X→→Y
成立当且仅当对R的任一关系r,r在(X,Z)上的每个值对应一组Y的值,这组值仅仅决定于X值而与Z值无关。
静态关系:一旦数据已加载, 用户只能在这个关系上运行查询操作, 不再进行插入、删除和更新操作。例如:安全模块中的权限表。
动态关系:经常被插入、更新、删除的关系。
静态关系只需具有第一范式,动态关系应该至少具有第三范式。但是要注意,不能说规范化程度越高的关系模式就越好,在设计数据库模式结构时,必须对现实世界的实际情况和用户应用需求作进一步分析,确定一个合适的、能够反映现实世界的模式。这也就是说,上面的规范化步骤可以在其中任何一步终止。
我们上面看到,一个不符合某一范式的关系模式,可以通过关系模式分解,变得符合某一范式。但是分解肯定不是随意的,这里我们介绍一些和关系模式分解有关的概念。
将一个关系模式分解成几个关系模式,但是这几个关系模式还是可以通过自然连接恢复为原来的关系,这样的分解具有无损连接性。
将一个关系模式分解成几个关系模式,通过自然连接恢复后,应该具有与原来完全相同的函数依赖。
不满足上述两条,就说明这个关系模式分解,丢失了信息。
当然大部分情况下我们一眼就能看出来,一个分解是否符合无损连接性。但是我们有一个算法,能从抽象的角度判断一个分解是否具有无损连接性。
我们有:给定关系模式R=(A1,A2...An)
R的函数依赖集F
一个分解p={R1,R2...Rk}
其中,每一列对应一个R的属性,每一行对应p中的关系模式,我们规定i为行号,j为列号,即用Aj和Ri表示列和行的表头值。基本就是这样的:
A1 A2 ... An
R1
R2
...
Rk
如果Aj属于Ri的属性集,i行j列填充aj
,否则填充bij
。
对于F中的每个函数依赖X->Y,如果X中所有元素所在所有列上有相同符号(即aj或bij),这些符号所在行数>=2,取出所有这样的行,取出属性组Y上所有列,取出交叉点。
修改所有交叉点,如果一列交叉点中有aj,将该列所有交叉点修改为第一个aj如果没有,将交叉点修改为bmj,m为所有交叉点所在行的最小行号。
如果在某次更改后,有一行成为:a1,a2,...,an,则算法终止。且分解p具有无损连接性,否则不具有无损连接性。
这个描述起来太复杂,其实做几道题就比较容易理解一些。
当关系模式R被分解为两个子模式时, 下述定理给出了一个判别无损连接性的简单方法。
我们有:关系模式R
p={R1, R2}是R的一个分解
U1、U2和U分别是R1、R2和R的属性集合
F是R的函数依赖集合
p具有无损连接性的充分必要条件是:
U1∩U2→U1-U2∈F+或U1∩U2→U2-U1∈F+
通常情况下,我们可以通过分解结果关系的语义和原关系语义比较来进行判别(这里说的语义就指函数依赖),用数学描述就是:
若F所逻辑蕴含的函数依赖一定也由分解得到的某个关系模式中的函数依赖Fi所逻辑蕴含,则称关系模式R的这个分解是保持函数依赖的。
这个比较好理解,就不多说了。