前面介绍了规范化的技术以及属性间的函数依赖的概念,还讲述了利用规范化技术支持数据库设计的好处,以及如何将样本表格中的属性规范化为第一范式(1NF)、第二范式(2NF),最终规范化为第三范式(3NF),等等。下面将继续讨论 3NF 以上的范式。
1. 函数依赖的进一步讨论
与规范化密切相关的概念之一就是 函数依赖,函数依赖定义了属性之间的联系(Maier,1983)。之前已经讲述了这一概念,本篇将通过对函数依赖推导规则的讨论,以更加形式化和理论化的方式继续讲述函数依赖。
1.1 函数依赖的推导规则
前文已经定义了规范化时最常用到的函数依赖的特性。然而,即使我们仅仅关注那些左边和右边具有一对一的联系、恒成立且右边完全函数依赖与左边的函数依赖,对于给定的关系来说,有所有满足上述要求的函数依赖构成的集合依然非常庞大,因此我们要找到一种方法,利用这种方法将该集合减小到一个易于处理的规模,这一点很重要。理论上,我们想要确定一个关系的函数依赖集(用 X 表示),X 要小于由该关系上成立的全部函数依赖构成的集合(用 Y 表示),并且 Y 中的每一个函数依赖都能够被 X 蕴涵。因此,如果我们支持了 X 中的函数依赖所定义的完整性约束,自然也就实现了 Y 中的函数依赖所定义的完整性约束。这种需求表明必须可以从一些函数依赖推导出另外一些函数依赖。例如,如果关系中存在函数依赖 A → B 和 B → C,则函数依赖 A → C 在该关系中也成立。A → C 就是一个前面介绍过的传递函数依赖的例子。
我们怎样开始着手确定关系中这些有用的函数依赖呢?通常,数据库设计人员首先确定那些语义上非常明显的函数依赖;但是,经常还会有大量的其他函数依赖的存在。事实上,在实际的数据库项目中,想要确定所有可能的函数依赖基本上是不现实的。然而,在这一节里,我们的确是在讨论一种帮助我们确定关系的所有函数依赖集和的方法,然后讨论如何获得一个能够表示这个所有集的最小函数依赖集。
被某一函数依赖集 X 所蕴含的所有函数依赖的集合称为 X 的 闭包,记为 X+。显然,我们需要一组规则帮助我们从 X 计算出 X+。被称为 Armstrong 公理的这套推导规则,规定了如何从已知的函数依赖推导出新的函数依赖(Armstron,1974)。为了便于讨论,我们用 A、B 和 C 表示关系 R 的属性子集。Armstrong 公理如下:
- 自反性(Reflexivity):若 B 是 A 的子集,则 A → B。
- 增广性(Augmentation):若 A → B,则 A,C → B,C。
- 传递性(Transitivity):若 A → B 且 B → C 则 A → C。
- 自确定性(Self-determination):A → A。
- 分解性(Decomposition):若 A → B,C,则 A → B,A → C。
- 合并性(Union):若 A → B 且 A → C,则 A → B,C。
- 复合性(Composition):若 A → B,C → D,则 A,C → B,D。
上述前三条规则都可以利用函数依赖的概念直接证明。给定一个函数依赖的集合 X 后,利用这三条规则就可以推导出所有 X 所蕴涵的函数依赖,因此 Armstrong 的规则是 完备的。规则又是 有效的,因为所有不被 X 所蕴涵的函数依赖都不会被推导出来。也就是说,可以利用 Armstrong 的三条规则导出 X 的闭包 X+。
从上述前三条规则还能推导出后续的四条规则,利用这些规则可以简化 X+ 的计算。在后四条规则中,D 也表示关系 R 的属性的一个子集。
规则 1(自反性)和 规则 4(自确定性)表明一组属性总能决定它的任意子集和它自身。因为用这两个规则导出的函数依赖总是成立的,所以这些函数依赖是平庸的(trivial),如前所述,是我们不感兴趣的、对规范化没用的依赖。规则 2(增广性)表明在依赖的左右两边同时增加一组相同的属性后得到的依赖仍然是有效地依赖。规则 3(传递性)表明依赖是可以传递的。规则 5(分解性)表明去掉依赖右边的一些属性后,依赖仍然成立。重复使用这个规则,就可以将函数依赖 A → B,C,D 分解为一组函数依赖 A → B、A → C 和 A → D。而规则 6(合并性)表明反之亦然,即我们可以将函数依赖 A → B、A → C 和 A → D 合并为一个函数依赖 A → B,C,D。规则 7(复合性)比规则 6 更一般化,它表明可以将一组不相重叠的函数依赖合并成一个新的依赖。
在开始确定一个关系的函数依赖集合 F 时,通常我们会首先根据属性的语义确定部分函数依赖。然后,应用 Armstrong 公理(规则 1~3)推导出其他的函数依赖。推导这些函数依赖的一种系统化的方法是首先确定所有会在依赖的左边出现的属性组 A,然后确定所有依赖于 A 的属性。这样,对于每一组属性 A,我们都可以确定一个属性集 A+,A+ 是基于 F 被 A 函数决定的属性的集合(A+ 被称为 A 在 F 下的闭包(closure))。
1.2 最小函数依赖集
这一小节将介绍函数依赖的 等价 集合。如果函数依赖集 Y 的每一个函数依赖都属于函数依赖集 X 的闭包 X+,就称 Y 被 X 覆盖,即 Y 中的每个依赖都能够从 X 导出。如果函数依赖集 X 满足下列条件 就称 X 是最小函数依赖集。
- X 中每个依赖的右边都只包含单个属性。
- 对 X 中任意依赖 A → B,不存在 A 的一个真子集 C,使得用依赖 C → B 替换依赖 A → B 后得到的函数依赖集仍旧和 X 等价。
- 从 X 中去掉任何依赖后的函数依赖集都与 X 不等价。
最小依赖集应该是一种没有冗余的标准形式。函数依赖集 X 的最小覆盖是与 X 等价的最小依赖集 X_min。不幸的是,一个函数依赖集可能有几个最小覆盖。
2. Boyce-Codd 范式(BCNF)
上一篇讨论了如何消除对主关键字的部分依赖和传递依赖。若关系上具有对主关键字的部分依赖和传递依赖则会产生更新异常。然而 2NF 和 3NF 前期定义并没有考虑对其他候选关键字的部分依赖和传递依赖的存在。前面对 2NF 和 3NF 的一般化定义,即不允许存在任何对候选关键字的部分依赖和传递依赖。应用 2NF 和 3NF 的一般化定义可以发现,由于对候选关键字存在部分的传递依赖而产生的冗余。然而,即使增加了这些附加约束,在 3NF 的关系中仍有可能存在一些会引起冗余的依赖。3NF 的这一不足导致了另外一种更强的范式的出现——Boyce-Codd 范式(Codd,1974)。
Boyce-Codd 范式的定义
Boyce-Codd 范式是在考虑了关系中所有候选关键字上的函数依赖的基础上设定的。尽管如此,同 前述的 3NF 的一般化定义相比较,BCNF 还添加了一些其他的约束。
Boyce-Codd 范式:当且仅当每个函数依赖的决定方都是候选关键字时,某一关系才是 BCNF 的。
为了验证一个关系是否是 BCNF 的,我们首先要确定所有的决定方,然后在验证他们是否都是候选关键字。回想一下决定方的定义:决定方就是一个或一组被其他属性完全函数依赖的属性。
3NF 和 BCNF 之间的区别表现在对于一个函数依赖 A → B,3NF 允许 B 是主关键字属性且 A 不是候选关键字;但是,BCNF 却要求在这个依赖中,A 必须是候选关键字。所以,Boyce-Codd 范式是增强的 3NF,每一个 BCNF 的关系也是 3NF 的,但是一个 3NF 的关系却不一定是 BCNF 的。
可能会违反 BCNF 的情况有:
- 关系中包含两个(或多个)合成候选关键字。
- 候选关键字互相重叠,通常至少都包含一个相同的属性。
在接下来的示例中,我们将给出一种违反 BCNF 范式的情况,并说明如何将这个关系转化为 BCNF 范式。这个实例给出了将 1NF 转化为 BCNF 关系的过程。
例 6.2 Boyce-Codd 范式(BCNF)
看如下一张表,表中关系为 DreamHome 的员工与客户进行会谈的记录。在会谈那天,参与会谈的员工被安排在一个指定的房间。但是,根据需要,在一个工作日里一个房间可能会被分配给多个员工。一个客户仅在指定的日期参与一次会谈,但有可能会在以后的日子进行其他的会谈。
clientNo | interviewDate | interviewTime | staffNo | roomNo |
---|---|---|---|---|
CR76 | 13-May-14 | 10.30 | SG5 | G101 |
CR56 | 13-May-14 | 12.00 | SG5 | G101 |
CR74 | 13-May-14 | 12.00 | SG37 | G102 |
CR56 | 1-Jul-14 | 10.30 | SG5 | G102 |
关系 ClientInterview 有三个候选关键字:(clientNo,interviewDate),(staffNo, interview-Date,interviewTime)和(roomNo, interviewDate, interviewTime),所以关系 ClientInterview 有三个合成候选关键字,它们都包含了一个共同的属性 interviewDate。我们选择(clientNo,interviewDate)作为主关键字。关系 ClientInterview 的结构如下所示:
ClientInterview (clientNo, interviewDate, interviewTime, staffNo, roomNo)
关系 ClientInterview 上成立的函数依赖如下所示:
fd1 clientNo, interviewDate → interviewTime, staffNo, roomNo (主关键字)
fd1 staffNo, interviewDate, interviewTime → clientNo (候选关键字)
fd1 roomNo, interviewDate, interviewTime → staffNo, clientNo (候选关键字)
fd1 staffNo, interviewDate → roomNo
现在我们分析一下这些函数依赖以确定关系 ClientInterview 的范式。由于函数依赖 fd1、fd2、fd3 都是该关系的候选关键字,所以这些依赖都不会给 ClientInterview 带来任何问题。唯一需要讨论的函数依赖是(staffNo, interviewDate) → roomNo(fd4)。尽管(staffNo,interviewDate)不是关系 ClientInterview 的候选关键字,但是由于 roomNo 是候选关键字(roomNo,interviewDate,interviewTime)的一部分,因此 roomNo 是主属性,所以这个函数依赖也是 3NF 所允许的。因为在主关键字(clientNo,interviewDate)上不存在部分依赖和传递依赖,并且函数依赖 fd4 也没有破坏 3NF 的条件,所以关系 ClientInterview 是 3NF 的。
但是,这个关系却不是 BCNF 的(一种增强的 3NF 范式)。因为决定方(staffNo, interviewDate)不是关系的候选关键字,而 BCNF 要求关系中所有的决定方都必须是候选关键字,所以关系 ClientInterview 可能会出现更新异常。例如,当我们要改变员工 SG5 在 2005 年 5 月 31 日进行会谈的房间编号时,就需要同时对两个元组进行更新。如果只更新了一个元组的房间编号,就会导致数据库的状态不一致。
为了将关系 ClientInterview 转化为 BCNF 范式,就必须消除不满足 BCNF 条件的函数依赖,为此,可以建立两个新的关系 Interview 和 staffRoom,如下表所示:
Interview:
clientNo | interviewDate | interviewTime | staffNo |
---|---|---|---|
CR76 | 13-May-14 | 10.30 | SG5 |
CR56 | 13-May-14 | 12.00 | SG5 |
CR74 | 13-May-14 | 12.00 | SG37 |
CR56 | 1-Jul-14 | 10.30 | SG5 |
staffRoom:
staffNo | interviewDate | roomNo |
---|---|---|
SG5 | 13-May-14 | G101 |
SG37 | 13-May-14 | G102 |
SG5 | 1-Jul-14 | G102 |
Interview (clientNo, interviewDate, interviewTime, staffNo)
staffRoom (staffNo, interviewDate, roomNo)
如上所示,我们可以将任何不是 BCNF 的关系分解成 BCNF 的关系。但是这种分解的结果并非总是我们想要的 BCNF 关系,比如说,如果再分解过程中丢失了某个函数依赖(也就是说,决定方和由它决定的属性被分配到了不同的关系中),其结果就并非我们所求。在这种情况下,也就很难实现该函数依赖,一个重要的约束也会因此而丢失。当发生这种情况时,最好将规范化过程只进行到 3NF,而只将关系分解到 3NF 是不会丢失任何依赖的。注意,在例 6.2 中,当我们将关系 ClientInterview 分解成两个 BCNF 的关系时,就已经 “丢失” 了函数依赖:roomNo,interviewDate,interviewTime → staffNo,clientNo(fd3)。因为这个以来的决定方被分解到了不同的关系中。但是,还必须认识到,如果不消除函数依赖 staffNo,interviewDate → roomNo(fd4),那么关系 ClientInterview 中将会存在数据冗余。
在规范化关系 ClientInterview 时,是规范化到 3NF 好还是继续规范化到 BCNF,主要考虑是由 fd4 导致的数据冗余产生的影响大还是由于 “丢失” fd3 造成的影响更大。例如,如果每个员工每天只与客户进行一次会谈,在这种情况下,关系 ClientInterview 上的函数依赖 fd4 的存在不会导致数据冗余,因此也就没有必要将关系 ClientInterview 分解成两个 BCNF 关系,即使分解,也没有什么额外的用处。相反,如果每位员工每天都会多次与客户进行会谈,那么函数依赖 fd4 的存在势必产生数据冗余,所以这时就要将 ClientInterview 规范化为 BCNF。然而,我们还应该考虑丢失 fd3 的影响,即 fd3 是否传达了关于客户会谈的一些重要信息,而且这些信息必须在结果关系中表现出来?对这个问题的回答可以帮助我们决定到底是保留所有的函数依赖还是消除数据冗余。
3. 规范化到 BCNF 的过程小节
4. 第四范式
尽管 BCNF 消除了由于函数依赖所带来的异常,但是进一步的研究表明,另外一种依赖—— 多值依赖(Multi-Valued Dependency,MVD),也会导致数据冗余(Fagin,1977)。
4.1 多值依赖
由于第一范式不允许元组任一属性的取值是值的集合,因此关系中就可能存在多值依赖。例如,如果某一关系有两个多值属性,为了保证关系中元祖的一致性,则其中一个属性的每一种取值,都不得不重复另一个属性的所有值。这种类型的约束就是多值依赖,多值依赖导致了数据冗余。考虑下表所示的关系 BranchStaff-Owner,该关系中包含了每个分公司(branchNo)的员工(sName)和业主(oName)。在这个例子中,假设每个员工的名字(sName)和每个业主的名字(oName)都是唯一的。
BranchStaffOwner:
branchNo | sName | oName |
---|---|---|
B003 | Ann Beech | Carol Farrel |
B003 | David Ford | Carol Farrel |
B003 | Ann Beech | Tina Murphy |
B003 | David Ford | Tina Murphy |
在本例中,员工 Ann Beech 和 David Ford 在分公司 B003 工作,业主 Carol Farrel 和 TinaMurphy 均是在分公司 B003 注册的。然而,由于在某公司工作的员工与在某分公司注册的业主之间没有直接的联系,因此需要创建将每个员工和业主关联在一起的元组以保证关联的一致性。关系 BranchStaffOwner 中的这种约束就是多值依赖,即 MVD 的存在是由于关系 BranchStaffOwner 中包含了两个独立的一对多关系。
多值依赖(Multi-Valued Dependency,MVD):表示关系中属性(如 A、B 和 C)之间的依赖,对于 A 的每一种取值,B 和 C 都分别有一个值集合与之对应,并且 B、C 的取值都是相互独立的。
属性 A、B 和 C 之间的 MVD 用如下的标记表示:
A ->> B
A ->> C
多值依赖又可以进一步分为 平凡的(trivial) 和 非平凡的(nontrivial)。对于关系 R 中的多值依赖 A ->> B,如果(a)B 是 A 的子集,或者(b)A ∪ B = R,那么多值依赖 A ->> B就是平凡的,否则 A ->> B 就是非平凡的。平凡 MVD 并不对应着关系中的任何约束,而一个非平凡的 MVD 则代表了关系上的一个约束。
在上述关系 BranchStaffOwner 包含两个非平凡 MVD,即 branchNo ->> sName 和 branchNo ->> oName,而 branchNo 不是该关系的候选关键字。因此,关系 BranchStaffOwner 受这些非平凡 MVD 约束:为了保证属性 sName 和 oName 之间关系的一致性,元组信息不得不重复。例如,如果要为分公司 B003 新增一位业主,为了保证关系的一致性就必须插入两个新的元组,每个元组对应着一名员工。这是一个由非平凡 MVD 的存在而引起的更新异常。很明显,我们需要一种范式来排除这种像 BranchStaffOwner 的关系结构。
4.2 第四范式的定义
第四范式(4NF):一个关系是 4NF 当且仅当对每一个非平凡多值依赖 A ->> B,A都是关系的候选关键字。
第四范式能够防止关系中存在那些决定方不是关系候选关键字的非平凡 MVD(Fagin,1977)。规范非 4NF 关系需要消除关系中那些引发问题的多值依赖,这可以通过将产生多值依赖的属性及其决定方的副本移到一个新的关系来中实现。
例如,将上述 BranchStaffOwner 分解为两个关系::
BranchStaff:
branchNo | sName |
---|---|
B003 | Ann Beech |
B003 | David Ford |
BranchOwner:
branchNo | oName |
---|---|
B003 | Carol Farrel |
B003 | Tina Murphy |
这两个关系都是 4NF 的,因为在关系 BranchStaff 中只存在平凡 MVD:branchNo ->> sName,而在关系 BranchOwner 中也只存在平凡 MVD:branchNO ->> oName。注意,由于 4NF 中没有冗余,所以也就消除了潜在的更新异常。例如,如果为分公司 B003 新增一名业主,那么只需要在关系 BranchOwner 中增加一个元组。
5. 第五范式
无论何时讲一个关系分解为两个关系,结果关系总是要具有无损连接的特性。无损连接特性是指将分解后的结果关系重新连接起来就能生成原关系。尽管比较少见,但有的时候需要讲一个关系分解为多个关系,这时就可以用连接依赖和第五范式来处理。下面简要的讲述无损连接依赖及其与第五范式的关系。
5.1 无损连接依赖
无损连接依赖:一种与分解有关的特性,该特性能够确保在通过自然连接运算将分解后关系重新组合起来时,不会产生谬误的元组。
在使用投影运算拆分关系时,这种分解方法可以很清楚的体现出无损连接依赖的特性。具体来说,就是我们在使用投影运算分解时要非常小心谨慎,要让过程可逆,即还可以通过连接投影的结果关系来重构原关系。由于这样的分解保留了原关系中的所有数据,并且没有产生附加的谬误元组,所以这样的分解就被称为 无损连接(也称为无损耗或无附加连接)分解。例如,上一节中的 BranchStaffOwner 的分解就具有无损连接的特性。也就是说,可以通过对关系 BranchStaff 和 BranchOwner 进行自然连接运算来重构最初的 BranchStaffOwner 关系。但是,有时候却需要讲一个关系无损连接额分解为两个以上的关系(Aho et al,1979),这些情况就是无损连接依赖和第五范式(5NF)所要关注的。
5.2 第五范式的定义
第五范式(5NF):一个关系是 5NF 当且仅当对于关系 R 的每个连接依赖(R1,R2,... ,Rn),每个投影包含原关系的一个候选关键字。
第五范式(5NF)能防止关系中存在哪些所关联投影不包含原关系的任意候选关键字的非平凡连接依赖(Fagin,1977)。不与候选关键字相关联的非平凡连接依赖十分少见,因此通常 4NF 都是 5NF 的。
连接依赖(Join Dependency,JD):连接依赖是依赖的一种。对于关系 R 及其属性的子集 A,B,...,Z,当且仅当 R 的每一个合法的元组都与其在 A,B,...,Z 上的投影的连接结果相同时,称关系 R 满足 连接依赖。