DBMS-2.3 数据库设计(3)——数据库规范化设计实现(3NF、BCNF模式分解)
本文章的素材与知识来自李国良老师和王珊珊老师。
-
关系模式分解的定义
一.关系模式分解的定义
1.定义
2.理解
(1)关系不丢失:所有子关系的属性集加起来 = 原关系的属性集。即分解中不能把某个属性给丢了。
(2)模式不冗余:分解出的若干个子关系间不能出现包含关系,如果子关系R1和子关系R2存在关系:R1含于R2,那么对分解出的整个子关系集来说,R1就是冗余的。
(3)依赖不丢失:对于一个子关系R1<U1,F1>,若其U1={ A,B,C },那么其F1就是 原关系F 的闭包F+中,所有仅由A、B、C这三个属性构成的函数依赖。对每一个子关系都是如此,即保证了分解中不能把某个函数依赖给丢了。
二.关系模式分解的原则
针对这两种原则,有两种分解方式:保持无损连接性的模式分解 和 保持函数依赖性的模式分解。
-
分解的无损连接性
一.模式分解保证无损连接性的定义
1.定义
2.示例
(1)无损连接分解不仅要求不能丢失信息,还要求不能出现冗余信息。即使分解后未丢失任何信息,但只要出现多余的信息,就不是无损连接分解。
(2)由上述例子可以看出,一般是无法直接由定义判断一个分解是否为无损分解,只能对分解后的子关系做自然连接后与原关系比较才能判断,但这种方式明显不适用于多数据。因此通常使用验证算法来判断一个模式分解是否具有无损连接性。
二.验证模式分解是否保持无损连接性的算法
1.算法
步骤(1):关系模式R分解为若干子关系,每行就是一个子关系,列就是所有属性
步骤(2):遍历每行每列,如果当前第i行这个子关系,拥有第j列这个属性,则把i行j列填入aj,否则填入bij
步骤(3):遍历每一个函数依赖X->Y,对于属性X,对于那些拥有属性X的子关系(即第X列上的值为a而不是b),它们的Y列都要设成相同的值,那么要设成什么值呢,这些关系的Y列只要有一个a,则全部都可以设为a,否则全部设为b。
2.示例
为了理解上述这些步骤,我们举一个分解例子,并按照上述步骤来逐步推荐,判断这个分解例子是否保持无损连接性。
对于关系R<U,F>,其属性集U={A,B,C,D},其函数依赖集F={A->B,A->C,B->D,D->C}。对该关系R进行一次分解,得到三个子关系:
R1=<U1,F1>,R1的属性集U1={A,B}
R2=<U2,F2>,R2的属性集U2={A,C,D}
R3=<U3,F3>,R3的属性集U3={B,D}
接下来就开始利用算法分析:
(1)构建表
表的每一行就是一个分解出的子关系,上述三个子关系每个子关系一行;表的每一列就是原关系的一个属性,原关系的四个属性每个属性一列。因此构造出一个3行4列的表:
(2)填充表
遍历每一列,针对每一列对应的属性,再遍历每一行,如果第j列对应的属性 在 第i行对应的子关系 的属性集中,则用aj标记;若不在,则用bij标记。
对属性A,遍历每一个子关系:A在R1中,A在R2中,A不在R3中,因此对第一列的填充结果为:
对属性B,遍历每一个子关系:B在R1中,B不在R2中,B在R3中,因此对第二列的填充结果为:
对属性C和D也是同理,最终在填充表这一步可以得到:
(3)修改表
遍历原关系的每一个函数依赖(注意是显示声明的函数依赖集F,而不是闭包F+),对于函数依赖X->Y,假设属性X在第1列,属性Y在第3列:找到所有第1列为a的子关系(也就是所有拥有属性X的子关系),构成子关系集M,M中所有子关系的第3列即属性Y对应的列都将修改为同一个值——若M中任一子关系的第3列为a(即只要有一个子关系拥有属性Y),则M中所有子关系的第3列都改为a;若所有子关系的第3列都为b(即所有子关系都没有属性Y),则M中所有子关系的第3列都改为b(改成bij,其中i和j是几无所谓,一般选最小的那个)。
对函数依赖A->B:拥有属性A的子关系有R1、R2,那么它们的属性B对应列都将改为同一个值;又因为R1拥有属性B,那么R1和R2的第2列就都可以改为a2,即:
对函数依赖A->C:拥有属性A的子关系有R1、R2,那么它们的属性C对应列都将改为同一个值;又因为R2拥有属性C,那么R1和R2的第3列就都可以改为a3,即:
对于其他函数依赖也同理,最终修改表得到:
(4)判断表
遍历表的每一行,只要有一行全为a,则该分解满足无损连接性。而上述分解结果明显是满足的。
其实判断表这一步不是非得到最后一步再进行,只要在修改表的过程中出现一行全为a,则直接得出结果,结束判断。
3.理解
(1)是否无损,就是要看几个分解关系连接成一个后,是否还能得到原关系的所有属性。
(2)因此在遍历每一个函数依赖,根据X->Y,把那些X列相同的分解关系Ri的Y列设成相同值,其实就是在假设进行合并后其他关系的函数依赖都作用到自己身上的结果。
(3)把所有函数依赖遍历一遍后,结果就是对每一个分解关系Ri来说,此时的所有列的值都为假设合并后所有函数依赖作用的结果。如果此时出现一行全为a,说明此种分解方式,能够合并成分解前的关系。
(4)当把所有函数依赖遍历一遍后,其实对每一个分解关系Ri来说,都是最终合并的结果,如果这所有最终合并的结果中能出现分解前的情况,则说明是无损连接。
(5)分解后每个子关系的函数依赖集,是原关系的函数依赖集的闭包在该子关系上的投影。例如子关系R1的属性集U1={ A,B},那么R1的函数依赖集F1就是原关系的函数依赖集F的闭包(注意是F+,不只是F)中所有仅由A、B构成的函数依赖。
三.保持分解无损连接性的定理
1.对于定理【1】的理解
(1)若(R1交R2)->(R2-R1),那么对R1来说,R1拥有了(R1-R2)、(R1交R2),又因为(R1交R2)->(R2-R1),那么以R1为基础就可以得到:(R1-R2) + (R1交R2) + (R2-R1) = { R1、R2 },即没有属性丢失,保持了无损连接性。
(2)(R1交R2)->(R1-R2)同理,不过是以R2为基础。
2.判断分解是否保持无损连接性的三种方式
(1)对所有关系做自然连接后得到的关系,与原关系比较判断——少用
(2)使用验证无损连接性的算法——最常用
(3)使用保持分解无损依赖的定理【1】——适用于只有两个子关系的分解
-
分解的保持函数依赖性
一.定义
(1)即分解要保持函数依赖性就是:原关系的函数依赖集的闭包 = 所有子关系的函数依赖集合并后构成的总函数依赖集的闭包。
(2)分解后每个子关系的函数依赖集,是原关系的函数依赖集的闭包在该子关系上的投影。
二.示例
三.分解的无损连接性 和 保持函数依赖性 之间的关系
1.关系
2.破坏分解的无损连接性或保持函数依赖性的原因
上述提到,无论是哪种分解,分解后的子关系的函数依赖集 就是 原关系的函数依赖集的闭包 在 该子关系上的投影。那么我们假设:
(1)一个关系R<U,F>,U={A,B,C,D},F={A->B,B->D,C->D}。接下来对R分解出两个子关系R1、R2
(2)R1的属性集U1={A,B,D},那么可以得出R1的函数依赖集F1={ A->B,B->D }
(3)R2的属性集U2={B,C},那么可以得出F2={},因为F在U2上并没有任何投影
(4)如此分解得到的R1、R2,可以看出这个过程丢失了函数依赖C->D
(5)因此,在分解过程中破坏无损连接性或保持函数依赖性的原因就是:原关系的函数依赖集 在根据子关系的属性集 而投影到子关系,构成子关系的函数依赖集时,丢失了函数依赖。
-
模式分解算法
一.分解方式与范式的对应关系
二.具有保持函数依赖性,并分解到3NF的算法
1.算法
2.口诀
保函依赖分解题,先求最小依赖集
依赖左右未出现,分成子集放一边
依赖左右成全集,提前结束开香槟
剩余依赖依左并,并完再添做子集
(1)“保函依赖分解题,先求最小依赖集”:先求出最小函数依赖集,后续都是基于最小函数依赖集进行判断的。
(2)“依赖左右未出现,分成子集放一边”:对于关系中的属性,若是未出现在任何函数依赖的两侧,则把这些属性作为一个子集,从原属性集中剔除,并放到一边。
(3)“依赖左右成全集,提前结束开香槟”:注意中间存在的一个可能提前终止的条件:将不出现在依赖两侧的属性作为子集U1,从原属性集U剔除后,对于剩下的U,如果Fm中存在一个函数依赖,其两侧的所有属性合起来就是U,那么就可以直接得到满足保持函数依赖性+3NF的分解结果,由两个子关系R1、R2构成,R1由属性集U1构成,R2由剔除U1后的属性集U构成。
(4)“剩余依赖依左并,并完再添做子集”:对于函数依赖集中那些左侧完全相同的函数依赖(根据最小函数依赖集的求法,非常有可能出现一些这样的函数依赖),把它们合并成一个函数依赖,并将依赖两侧的所有属性构成一个子集,放到一边。重复该步骤直到函数依赖集中所有函数依赖左侧均不相同。
(5)上述放到一边的所有子集,每个子集就是一个子关系,构成了保持函数依赖且分解到3NF的模式分解结果。
三.具有无损连接性和保持函数依赖性,分解到3NF的算法
1.算法
2.口诀
算法3.5是在算法3.4的基础上进行的,因此口诀也是以3.4的为基础
保函依赖分解题,先求最小依赖集
依赖左右未出现,分成子集放一边
依赖左右成全集,提前结束开香槟
剩余依赖依左并,并完再添做子集
若要连接成无损,先看是否有候选
若是子集无候选,再添候选做子集
(1)先求出保函+3NF的模式分解
(2)“若要连接成无损,先看是否有候选”:观察保函+3NF模式分解的结果,看是否有任一子关系,其属性集已经包含了原关系R的主键,若是有,则该模式分解结果同时也是保函+无损+3NF的模式分解结果,无需再额外处理。
(3)“若是子集无候选,再添候选做子集”:如果保函+3NF模式分解的结果中,没有任一子关系的属性包含了原关系R的主键,则求解原关系R的主键Key,并将Key的所有属性作为一个新的子关系并入原模式分解,得到保函+无损+3NF模式分解的结果。
3.示例
假设最终模式分解结果为p,先令p={ }
(1)“保函依赖分解题,先求最小依赖集”:求解Fm的算法可以看上篇文章
右:Fm={ A->B,A->E,AE->F,BC->D,D->A }
左:Fm={ A->B,A->E,A->F,BC->D,D->A }
中:Fm={ A->B,A->E,A->F,BC->D,D->A }
=>Fm={ A->B,A->E,A->F,BC->D,D->A }
(2)“依赖左右未出现,分成子集放一边”:
可以看出没有任一属性,未出现在所有函数依赖中
(3)“依赖左右成全集,提前结束开香槟”:
可以看出没有任一函数依赖,其左右属性合并可以构成U
(4)“剩余依赖依左并,并完再添做子集”:
Fm={ A->BEF,BC->D,D->A }
p = { { A,B,E,F },{ B,C,D },{ D,A } }
(5)“若要连接成无损,先看是否有候选”:
求解原关系R的候选键:Key={ A,C } 、{ B,C }、{ C,D }。 求解候选键的算法可以看上篇文章
可以看出p包含了一个候选键{ B,C }或{ C,D },因此提前结束
p = { ABEF,BCD,DA }就是保函+无损+3NF的模式分解结果
四.具有无损连接性,分解到BCNF的算法
1.算法
2.口诀
BCNF分解题,初始化p后遍历
左不包侯不含右,对该关系一拆二
一是左右成子集,一是原集剔除右
(1)“BCNF分解题,挨个遍历p关系”:最开始令p只有一个关系,就是原关系R,后续挨个遍历p中每一个关系,对每个关系判断其是否满足BCNF,若都满足则直接得到结果。
(2)“左不包侯不含右,对该关系一拆二”:如果p中存在一个子关系S,S中存在一个函数依赖X->Y,其中X不包含S的候选键,且X也不包含Y,那么S一定不满足BCNF范式,接下来就是把子关系S一拆二。
(3)“一是左右成子集,一是原集剔除右”:把不满足BCNF范式的子关系S拆成S1和S2,其中S1的属性集U1={ X,Y },即不满足条件的函数依赖的左右属性凑成一个子集;S2的属性集U2 = { S的属性集U - Y },把Y剔除后,相当于除去了罪魁祸首X->Y,那么原关系S就满足BCNF了。将p中的S替换成S1和S2,然后继续遍历p中下一个子关系。(注意,拆分后S1是肯定满足BCNF的,但S2仍有可能不满足BCNF)
3.示例
(1)“BCNF分解题,初始化p后遍历”:
令p={ R },此时R的候选键为Key= { B,E },很明显R不满足BCNF范式,继续进行。
(2)“左不含侯不含右,对该关系一拆二”:
R中的函数依赖A->C,其中左侧A不包含R的候选键,也不包含右侧C,因此对R拆分成R1和R2
(3)“一是左右成子集,一是原集剔除右”
R1= { A,C },R2={ A,B,D,E },p={ R1,R2 } = { AC,ABDE }
(4)“BCNF分解题,初始化p后遍历”:
接下来遍历到R2,R2的属性集U2={ A,B,D,E },R2的函数依赖集就是F+在U2上的投影,因此
F2={ A->D,B->D,DE->A ,BE->A },
此时R2的候选键为Key2={ B,E },很明显R2不满足BCNF范式,继续进行。
(5)“左不含侯不含右,对该关系一拆二”:
函数依赖A->D不满足该条件,因此对R2拆分为R21和R22
(6)“一是左右成子集,一是原集剔除右”
R21={ A,D },R22={ A,B,E },p = { R1,R21,R22 } = { AC,AD,ABE }
(7)“BCNF分解题,初始化p后遍历”:
上述分解出的R1、R21肯定满足BCNF,接下来看R22,
U22={ A,B,E },F22={ BE->A },
很明显R22是满足BCNF范式的,至此,p中的三个子关系都满足BCNF,因此p={ AC,AD,ABE }就是分解结果
(8)需注意的是:BCNF的分解结果并不是唯一的。该解题步骤的(5)中,选取了不满足条件的函数依赖A->D,但实际上在R2中,也有其他函数依赖不满足条件,例如B->D、DE->A,若是选取其他函数依赖去做拆分,也能得到不同但是正确的结果。
4.例1
设有关系模式R<U,F>,R={ A,B,C,D,E,F },F={ A->B,B->C,AD->F,D->E },将R按无损+BCNF拆分。
(1)先把F+求出来,由于后续子关系的函数依赖集都是以F+为基础进行投影,因此F+要尽可能写全。F+={ A->B,A->C,B->C,AD->F,D->E }
(2)p={ R },R的候选键Key={ A,D },很明显A->B不满足要求,拆分
(3)将R拆成R1、R2,R1={ AB },R2={ ACDEF },p={ R1,R2 }
(4)R2的函数依赖集F2={ A->C,AD->F,D->E },R2的候选键Key2={ A,D },很明显A->C不满足要求,拆分
(5)将R2拆成R21、R22,R21={ AC },R22={ ADEF },p={ R1,R21,R22 }
(6)R22的函数依赖集F22={ AD->F,D->E },R22的候选键Key22={ A,D },很明显D->E不满足要求,拆分
(7)将R22拆成R221、R222,R221={ DE },R222={ ADF },p={ R1,R21,R221,R222 }
(8)R222的函数依赖集F222={ AD->F },满足BCNF范式,至此p中所有关系模式均满足BCNF范式。
(9)最终分解结果p={ AB,AC,DE,ADF }
5.例2
设有关系模式R<U,F>,R={ A,B,C,D,E },F={ A->C,C->D,B->C,DE->C,CE->A },将R按无损+BCNF拆分。
(1)F+={ A->C,A->D,C->D,B->C,DE->C,DE->A,CE->A,BE->A }
(2)p={ R },R的候选键Key={ B,E },A->C不满足条件,拆分
(3)将R拆成R1、R2,R1={ AC },R2={ ABDE },p={ R1,R2 }
(4)R2的函数依赖集F2={ A->D,DE->A,BE->A },R2的候选键Key2={ B,E },A->D不满足条件,拆分
(5)将R2拆成R21、R22,R21={ AD },R22={ ABE },p={ R1,R21,R22 }
(6)R22的函数依赖集F22={ DE->A,BE->A },R22的候选键Key22={ B,D,E },R22满足BCNF范式。
(7)因此最终分解结果p={ AC,AD,ABE }
6.例三
设有关系模式R<U,F>,R={ A,B,C,D,E },F={ A->BC,CD->E,B->D,E->A },将R按无损+BCNF拆分。
(1)F+={ A->BCDE,CD->E,CD->A,B->D,E->A }
(2)p={ R },R的候选键Key={A}、{E}、{CD},B->D不满足条件,拆分
(3)将R拆成R1、R2,R1={ BD },R2={ ABCE },p={ R1,R2 }
(4)R2的函数依赖集F2={ E->A } ,满足BCNF范式。
(5)因此最终分解结果p={ BD,ABCE }。