范化与范式
函数依赖
函数依赖:关系表R 中取两个子集α 和β ,如果对于R 中任意两个元组t1 和t2 在α 各属性相等的时候也满足β属性都相等,则记α→β。(t1[α]=t2[α]⇒t1[β]=t2[β])
超键与候选键
超键:当且仅当K→R时,才认为K是R的超键。
候选键:K→R且不存在任何一个K的子集Ki使得Ki→R
平凡函数依赖
平凡函数依赖:如果β⊆α,我们认为α→β是平凡的。
无损分解
无损分解:将一个关系表R拆分成两个关系表R1和R2,如果ΠR1⋈ΠR2=R,则认为是无损分解;如果R⊂ΠR1⋈ΠR2,则是有损分解。
无损分解的充分条件:R1∩R2→R1或R1∩R2→R2。也就是说证得其一可知其为无损分解。
函数依赖的闭包
函数依赖的闭包:给定一个函数依赖的集合F,将所有能从F中推出的函数依赖构成的集合记作该集合的闭包F+。
当且仅当关系R中的一个函数依赖存在于闭包F+中时,它才成立。
函数依赖的闭包的获取方法:阿姆斯特朗公理
- 自反律:如果β⊆α,则α→β。
- 增补律:如果α→β,则γα→γβ
- 传递律:如果α→β且β→γ,则α→γ。
还有其它几个规则,可以由上面这些推出:
- 合并律:如果α→β且α→γ,则α→βγ。
- 分解律:如果α→βγ,则α→β且α→γ。
- 伪传递率:如果α→β且γβ→δ,则αγ→δ。
需要注意的是,这里面αβ并不是集合意义上的交集,而是并集。
具体算法:每轮循环中,先使用自反律和增补律,然后使用传递率。
属性集合的闭包
属性集合的闭包:将集合α在函数依赖集合F下的闭包记作α+。
计算流程:
- 初始化:结果集合result初始化为α。
- 循环以下过程:如果F中某个函数依赖β→γ满足β是result的子集,则将γ添加到result中。
判断属性集合α是否为超键的方式:判断其闭包α+是否与关系集合R等价。
判断函数依赖α→β是否成立(函数依赖是否在函数的闭包中)的方式:判断β⊂α+是否成立。
函数依赖的闭包的获取方法2:对于R的每一个子集γ,求它的闭包γ+,且对每一个γ+的子集S,输出一个函数依赖γ→S。所有输出的交集即是函数依赖的闭包。
依赖保持和它的验证方法
依赖保持:在验证分解后的子关系上的函数依赖时,无需进行笛卡尔积即可进行验证。(一个说法是,任意函数依赖中的所有属性都必须在同一个子关系中。)
依赖保持的新定义:将一个表格R拆分成R1,R2,...,Rn,又将F+中只包含Ri中属性的函数依赖的集合称作“F在Ri上的限定”Fi,则当(F1∪F2∪...∪Fn)+=F+时,F中所有依赖都被F′逻辑蕴含,从而实现了依赖保持。
一种避免计算F+的验证依赖保持的方法:
- 初始时刻result=α
- 不断重复以下过程:对于每个Ri,t=(result∩Ri)+∩Ri,result=result∪t。(这里计算闭包使用的是整个F)
- 当result没有变化时,如果result包含β中的所有属性,则α→β得到保持。
这个不断重复的过程其实相当于计算Fi下的result闭包,对每个Ri进行重复后得到F′下的result闭包。
无关属性
无关属性:在函数依赖集合F中的某个函数依赖中,删去之后不会影响F+的属性。
无关属性的判定:对于一个函数依赖α→β,
如果A∈α且根据F可以推出(F−{α→β})∪{(α−A)→β},则认为A是无关属性;如果A∈β且根据(F−{α→β})∪{α→(β−A)}可以推出F,则认为A是无关属性。
上面这个“可以推出”判断起来比较复杂,可以通过判断“属性的闭包是否包含新属性”来代替。
检验A∈α是否是无关属性:只需检查(α−A)→β是否可以从F中推断出(成立)即可。使用F计算(α−A)+,如果β⊂(α−A)+则得证。
检验A∈β是否是无关属性:只需检查从β中去除A之后还能否推出α→A。使用F′=(F−{α→β})∪{α→(β−A)}计算α的新闭包α+,如果A∈α+则得证。
正则覆盖
正则覆盖:一个函数集合F的正则覆盖Fc满足如下条件:
- F可以推出Fc
- Fc可以推出F
- Fc中的任何函数依赖都不包含无关变量
- Fc中任意两个函数依赖的左侧都两两不同。
正则覆盖的计算方法:
- 初始时设Fc=F
- 将所有形如α→β1和α→β2合并为α→β1β2
- 在Fc中遍历各函数依赖,如果发现一个无关属性,则将其删除。但是每个循环只删除一个。
- 重复以上过程,直到Fc不再发生变化。
BCNF
BC范式(BCNF):如果F+中的所有函数关系α→β都满足:
- α→β是平凡的(即β⊂α)
- 或者α是关系R的超键
则认为这个关系表R满足BCNF。当F+是空集的时候,也认为该关系表满足BCNF。
F+可以通过将属性随机组合来判断是否存在函数依赖。
拆分关系R以满足BC范式:如果关系R中α→β不满足BCNF,则将R分成α∪β和R−(β−α)。
BCNF的检测方法
检查α→β是否违反BCNF:如果不是平凡函数依赖,则计算α+,当其为关系R的超键时认为不违反。
简化的检查方法:只检查一个给定的函数集合F是否满足BCNF,而不是F+。不过在检测R的分解是否满足BCNF时不能用这个方法,容易漏检。
分解后的BCNF检测方法:
- 遍历F在Ri上的限定(F+中只包含Ri中属性的函数依赖的集合)中的所有函数依赖,测试是否满足BCNF。
- 或使用原有的函数依赖F。对于任意一个属性子集α⊆Ri,验证α+不包括Ri−α中的任何属性或包括Ri中的所有属性。(前者对应BCNF中的“平凡”条件,后者对应“超键”条件。)如果BCNF被某个函数依赖α→β打破,则一定存在一个α→(α+−α)∩Ri在Ri上存在。
BCNF的分解算法
BCNF分解算法:
- 给结果集合赋初值:result={R}(注意这是一个关系表的集合)
- 计算F+
- 如果result中存在一个Ri不满足BCNF,则找到其中一个违背BCNF且α∩β=∅的函数依赖α→β,将result更新为(result−Ri)∪(Ri−β)∪(α, β)(即,从result中去掉Ri,然后向其中添加Ri−β和(α,β))。循环执行这一步。
3NF
三范式(3NF):如果F+中的所有函数关系α→β都满足:
- α→β是平凡的(即β⊂α)
- 或者α是关系R的超键
- 或者β−α中的每个属性都包含在R的某个候选键中
则认为这个关系表R满足3NF。
3NF的检测方法
3NF检测方法:
- 只需检测F中的函数依赖而非F+
- 使用属性集合的闭包检测对于每个函数依赖α→β,α是否为超键。
- 如果α不是超键,检测β中的每个属性是否包含在R的某个候选键中。
3NF的分解算法
3NF分解算法:
- 获得F的正则覆盖Fc
- 初始化i=0
- 对于Fc中的每个函数依赖α→β,如果在1≤j≤i中没有任何一个关系Rj包含αβ,则i自增1,添加Ri=αβ;
- 如果在1≤j≤i中没有任何一个关系Rj包含R的候选键,则i自增1,添加Ri=R的任意一个候选键;
- 如果某个关系包含了另一个关系,则删除被包含的冗余关系;
- 得到分解后的Ri的集合
或者用一种更接近自然语言的方法描述:
初始情况下,分解后的关系表构成的集合为空集。对于F的正则覆盖中的每个关系α→β,如果没有任何一个分解后的关系表包含αβ,则将其作为一个分解后的关系表添加到集合中。遍历结束后,如果没有任何一个分解后的关系表包含R中的任意一个候选键,则选择一个候选键作为一个分解后的关系表添加到集合中。最后,删除重复的关系表。
多值依赖
多值依赖:将R的属性分为α,β和R−α−β三部分,如果对于任意一对元组t1和t2,存在t3和t4满足:

则认为α→→β,同时有α→→R−α−β。
注意:如果有α→β则一定有α→→β。
多值依赖的闭包:如果记函数依赖和多值依赖的集合为D,则将所有D逻辑蕴含的函数依赖和多值依赖的集合称为多值依赖的闭包D+。可以根据其二者的定义计算出多值依赖的闭包。具体规则不做展开。
平凡多值依赖:α∪β=R或者β⊂α。
第四范式
第四范式(4NF):所有D+中的形如α→→β的多值依赖满足以下两个条件之一:α→→β是平凡的,或者α是R的一个超键。
如果一个关系符合第四范式,它一定符合BCNF。
D在集合Ri上的限定Di:包含D+中所有只含Ri中属性的函数依赖或者所有形如α→→β∩Ri的多值依赖,其中α⊆Ri且α→→β∈D+。
4NF分解算法:与BCNF的很像
- 给结果集合赋初值:result={R}(注意这是一个关系表的集合)
- 计算D+,并用Di表示D在集合Ri上的限定
- 如果result中存在一个Ri不满足4NF,则找到其中一个违背4NF且α∩β=∅的多值依赖α→→β,将result更新为(result−Ri)∪(Ri−β)∪(α, β)(即,从result中去掉Ri,然后向其中添加Ri−β和(α,β))。循环执行这一步。