数据库系统基础教程
发布时间:2024-04-07 23:04:24浏览次数:49世界著名计算机教材精选数 据 库 系 统 基 础 教 程[ 美]Jeffrey D.U llman,Jennifer Widom著史嘉权 等译 清华大学出版社 - 骇http: / /www.tup.tsinghua.edu.cn PRENTICE HALL - 骇http: / /www.prenhall.com
5. 8. 5 对涉及到视图的查询的解释 216………………………………………………………5. 8. 6 本节练习 217……………………………………………………………………………5. 9 空值和外部连接 218………………………………………………………………………………5. 9. 1 对空值的运算 218………………………………………………………………………5. 9. 2 真值 U NKNOWN 219…………………………………………………………………5. 9. 3 SQL2 中的连接表达式 221………………………………………………………………5. 9. 4 自然连接 222……………………………………………………………………………5. 9. 5 外部连接 222……………………………………………………………………………5. 9. 6 本节练习 224……………………………………………………………………………5. 10 -SQL3 中的递归 225………………………………………………………………………………5. 10. 1 在 SQL3 中定义 IDB 关系 225………………………………………………………5. 10. 2 线性递归 228…………………………………………………………………………5. 10. 3 在WITH语句中使用视图 228………………………………………………………5. 10. 4 分层求反 229…………………………………………………………………………5. 10. 5 SQL3 递归中的未定表达式 230………………………………………………………5. 10. 6 本节练习 232…………………………………………………………………………5. 11 本章总结 233……………………………………………………………………………………5. 12 本章参考文献 235………………………………………………………………………………第 6 章 SQL中的约束和触发程序236………………………………………………………………6. 1 SQL中的键码 236…………………………………………………………………………………6. 1. 1 说明键码 236……………………………………………………………………………6. 1. 2 实施键码约束 238………………………………………………………………………6. 1. 3 本节练习 238……………………………………………………………………………6. 2 参照完整性和外键码 238…………………………………………………………………………6. 2. 1 说明外键码约束 239……………………………………………………………………6. 2. 2 保持参照完整性 240……………………………………………………………………6. 2. 3 本节练习 241……………………………………………………………………………6. 3 对属性值的约束 243………………………………………………………………………………6. 3. 1 非空约束 243……………………………………………………………………………6. 3. 2 基于属性的CHECK约束 243…………………………………………………………6. 3. 3 域约束 244………………………………………………………………………………6. 3. 4 本节练习 245……………………………………………………………………………6. 4 全局约束 246………………………………………………………………………………………6. 4. 1 基于元组的CHECK约束 246…………………………………………………………6. 4. 2 断言 247…………………………………………………………………………………6. 4. 3 本节练习 250……………………………………………………………………………6. 5 约束的更新 251……………………………………………………………………………………6. 5. 1 对约束命名 251…………………………………………………………………………6. 5. 2 更改表的约束 252………………………………………………………………………6. 5. 3 更改域的约束 253………………………………………………………………………6. 5. 4 更改断言 253……………………………………………………………………………·Ⅸ·
键码的其他术语在一些书和论文中, 可能会发现关于键码的不同术语。可能会碰到把术语“键码”( key) 用于我们使用术语“超键码”( superkey) 的场合, 也就是说, 是函数 决定所有属性的属性集, 但没有最小化要求。这些资料一 般使用术语“候选键码 ”(candidate key) 来表示最小的键码—— 也就是我们用“键码”这个术语所表示的意思。例 3. 22 在例 3. 21 的关系中, 有许多超键码。不仅键码{title, year, starName}本身是超键码, 而且该属性集的任何超集, 比如{tit le,year,starName,lengt h}都是超键码。 □3. 5. 4 寻找关 系的键码如果一个关 系模式是 从ODL或E/R设 计转换成 关系而推 导出来 的, 通 常我们可 以预测该关系的键码。我们在本节将考虑来自E/R图的关系; 3. 5. 5 节将讨论来自ODL设计的关系。如果关系来自ODL或E/R设计, 通常( 但不绝对) 每个关系只有一个键码。当关系只有一个键码时, 在关系模式中将键码属性用下划线标出是个有用的约定。推断键码的第一条规则是:· 来自实体集的关系的键码就是该实体集或类的键码属性。例 3. 23 在 例 3. 10 和 3. 11 中, 我 们描述 了 实体 集 Movies 和 Stars 如 何转 换成 关系。这两个实体集的键码分别为{tit le,year}和{name}。因此, 它们也是相应关系的键码,并且Movie( - tit le, - year, length, filmType)Stars( - name,address)分别是相应的关系模式, 其中用下划线标出的是关系的键码。 □第二条规则涉及 到二元联系。如果 关系R来自一个 联系, 则该 联系的多样性将 会影响R的键码。有三种情形:· 如果联系是“多对多”的, 则相连的两个实体集的键码都是 R 的键码属性。· 如果是 从实体 集E1 到实 体集E2 的“多 对一”联系, 那么实 体集E1 的 键码属性 是R的键码属性, 而E2 的键码属性则不是R的键码属性。· 如果联系是“一对一”的, 则联系两端的任何一个实体集的键码属性都是R的键码属性, 即 R 的键码不是唯一的。例 3. 24 例 3. 12 讨论了联系Owns( 拥有) , 它是从实体集Movies到实体集Studios的“多对一”联系。因此, 关系Owns的键码是来自Movies的键码属性title和year。Owns的关系模式如下, 其中用下划线标出的是键码属性:Owns( - 躬title, - 躬year,st udioName)另一 方面, 例 3. 13 讨论 了实体集Movies和Stars之间的“多 对多”联 系Stars-in( 主演) 。现在, 相应关系的所有属性都是键码属性。Stars-in( - 粚tit le, - 粚year, - 粚st arName)·58·
函数依赖的其他概念我们的观点是函数依赖的左边可以有多个属性, 但右边只能有一个属性。而且, 右边的属性还不能出现在左边。但是, 允许左边相同的几个函数依赖合并成简化形式, 于是就得到右边有多个属性的函数依赖。我们还发现允许出现“平凡依赖”( 这是指, 右边的属性是左边的属性之一) 有时会带来方便。关于这个问题 的其他著作通 常从这样 的观点 出发: 左右两边 都是任 意属性集, 并且任何属性都可能在两边同时出现。这两种方式没有重大区别, 但除非特别声明, 我们将继续沿用这种观点—— 函数依赖中, 不存在同时出现在两边的属性。事实上, 只有在多对多联系本身具有属性的情况下, 该联系对应的关系才不能将其所有属性作为键码的组成部分。于是, 联系本身的属性将从键码中略去。 □最后, 让我们考虑多向联系。由于我们不能用从该联系引出的箭头来描述所有可能的依赖, 就存在这样的情形: 如果不 仔细考虑哪 些实体集 函数决定 另一些 实体集的 话, 键 码是不那么容易看出来的。不过, 我们可以保证:· 如果多向联系 R 有一个箭头指向实体集 E , 则相应的关系中, 除了 E 的键码以外,至少还存在一个键码。3. 5. 5 由 ODL 设计导出的 关系的键 码当我 们把ODL设计 转换 成关系 时, 情况 有点复 杂。首先, 一 个ODL类可能 有一 个或多个说 明的 键码, 但在 属性 之中 也可能 根本 没有键 码。解 决后 一种 情况, 正如 我们 在3. 2. 6节所讨论的, 必须在相应的关系中引入一个属性, 代替该类对象中的对象标识。但是, 无论是 ODL 类拥有由 自己的属性组成的 键码, 还 是必须 把代替对 象标识的 属性作为键码, 在某些情况下, 类的键码属性将不是关系的键码。原因是, 在使用我们介绍的从ODL转换到关系的方法时, 有时一个关系中包含了太多的信息。这个问题在类的定义中包含联系( 译注: 原文误为“关系”) 时尤为突出。首先, 假设类C有一个指向某个类D的单值联系R。然后, 我们像 3. 2. 4 节中建议的那样, 在C的关系中包含D的键码。C的键码依然是相应关系的键码。当 C 有一个指向某个类 D 的多值联系 R 时, 问题就出现了。如果 R 的反向联系是反向的单值联系( 即,R是个“一对多”的联系) , 则像 3. 2. 7 节的方框“联系的单向表示”中提示的那样, 我们只能在 D 的关系中表示 R 的反向联系。R 的反向联系在 D 的关系中表示是毫无问题的, 因为它是D中的单值联系。但 是, 假设R是“多对 多”的联 系, 也就是 说,R及其反 向联 系在C和D中都 是多 值的, 则为 C 构造的关系中可能要用多个元组来表示类 C 的一个对象。结果, C 的键码就不是相应关系的键码。而我们不得不把D的键码加入到C的键码中, 来组成关系的键码。例 3. 25 在例 3. 7 我们为ODL的类Movie构造的关系中, 除了Movie的属性, 还增加了:1. 类Studio(Movie通过单值联系ownedBy与之相连) 的键码st udioName;·68·
2. 类 Star( Movie 通过多值联系 stars 与之相连) 的键码 starName。第一 项来 自单 值联系, 对Movie关 系的键 码并 没有 影响。然而, 第 二项 来自 多值 联系, 必须加入到 Movie 关系的键码中, 因此, 键码就变成了:{title,year,starName}检验一 下图 3. 13 中的Movie关 系实例, 可 以看 出title和year本 身并 不是 键码, 但加 上starName 就足以构成键码了。 □总之, 如果C对应的 关系表 示了C中的几个 多值联 系, 则 所有这 些多 值联系 所连 接的类的键码都必须加入到 C 的键码中; 得到的结果才是 C 对应的关系的键码。当然, 如果多个联系将 C 与同一个类 D 相连, 则 C 对应的关系中需用不同的属性表示每个从 C 到 D的联系所连接的类D的键码。由于这 个悖论, 如果我 们按照 3. 2 节所 给的构 成关系的 方法行 事的话, ODL 类的 键码可能不足以构成相应关系的键码。通常我们需要修正用这种简单方法构造的关系。改善这些关系的方法将在 3. 7 节介绍。我们将会看到, 把“多对多”联系从任一个相连的类所对应的关系中分离出来, 是可能的。最终的关系模式的 聚集看起来将更像 从许多 E/ R 设计直接得到的关系模式。①3. 5. 6 本节练 习练习 3. 5. 1: 考 虑一个关于美国 人的关系, 包括姓名、社会保险号、街道地址、城市、州、邮政编码( ZIP Code) 、地区码以及电话号码( 7 位) 。你认为有哪些函数依赖? 该关系的键码是什么? 要回答这些问题, 就需要了解这些数字分配的方法。例如, 地区码能跨两个州吗?邮政编码能跨两个地区码吗? 两个人的社会保险号能相同吗? 他们的地址或电话号码能相同吗?* 练习 3. 5. 2: 考虑一个表示 封闭容器中分子 当前状态的关系。属性 集是一个分子的 标识, 包括该分子的x,y,z坐标和它在x,y,z三维空间的速度。你认为有哪些函数依赖? 该关系的键码是什么?! 练习 3. 5. 3: 在练习 2. 3. 2 中, 我们讨论了联系出生(Births) 的三种不同的假定。对于每种情况, 标出从该联系构造的关系的键码。! 练习 3. 5. 4: 假设关系 R 具有属性 A1 , A2 , …, An 。说出下列情况下, R 有多少个超键码( 是n的函数) :* (a) 只有键码A1 。(b) 只有键码A1 和A2 。(c) 只有键码{A1 ,A2 }和{A3 ,A4 }。(d) 只有键码{A1 ,A2 }和{A1 ,A3 }。·78·①当 然, 我们也 可以 先把ODL设计转 换为等 价的E/R设 计, 再把E/R设计 转换为 关系 设计。尽管 这种方 法对3. 2 节 的直 接转换 方法 固有的 某些 问题采 取了措 施, 但毕 竟不是 根本 的解决 办法 。而我 们无 论如何 都需要 了 解的 3. 7节的关 系设 计技术 , 至 少可 以把这 个问 题解决 得同样 好。
3. 6 函数依赖规则这一节我们将学习如何推导函数依赖。也就是说, 假设我们已知某关系所满足的函数依赖集, 在不知道该关系的具体元组的情况下, 通常可以推断出该关系必然满足的其他某些函数依赖。在 3. 7 节讨论设计良好的关系模式时, 这种揭示其他函数依赖的能力是至关重要的。例 3. 26 如果 已知关系R拥有 属性A,B和C, 它满足如 下函数 依赖:A→B和B→C, 则我们可以断定R也满足依赖A→C。那么我们是怎样推导的呢? 为了证明A→C, 需要考察 R 的任意两个在属性 A 上取值一致的元组, 证明它们在属性 C 上也取值一致。设 两个在 属性A上一 致的元组 为(a,b1 ,c1 ) 和(a,b2 ,c2 ) 。 假设元 组中 的属性 顺序 为A, B, C。由于 R 满足 A→B, 并且这两个元组在 A 上一致, 它们在 B 上也必然一致。也就是说,b1 =b2 。所以这两个元组实际上就是(a,b,c1 ) 和(a,b,c2 ) , 其中b就是b1 或b2 。同样,由于R满足B→C, 而且两个元组在B上一致, 则它们必然在C上一致。因此,c1 =c2 ; 也就是说, 两个元组的确在 C 上一致。我们已经证明了任意两个在 A 上一致的 R 的元组在 C上也一致, 而这就是函数依赖A→C。 □在不改变关系的合法实例集的条件下, 函数依赖通常可以用几种不同的方式来表示;如果满足这种情况, 我们就称这样的两个函数依赖集是等价的(equivalent) 。更一般地, 如果满 足 T 中 所 有依 赖的 每个 关系 实 例都 满足 S 中 的所 有依 赖, 我们 就称 函 数依 赖集 S“蕴含于”(follow from) 函数依赖集T。注意, 如果S蕴含于T, 同时T也蕴含于S, 则S和T两个函数依赖集是等价的。本节将介绍几个有用的函数依赖规则。通常, 利用这些规则, 我们可以用等价的依赖集来代替某个 依赖集, 或 者在一个函数依 赖集中加 入蕴含于 同一个 函数依赖 集的其他 依赖 集。举例 来说,“转 换规 则”( transitive rule) 就 可以 使我 们像例 3. 26 中 那 样跟 踪函 数依赖链。我们 也 将给 出 一个 算 法来 判 断 一个 函 数依 赖 是否 蕴 含于 一 个 或多 个 其他 函 数依赖。 3. 6. 1 分解/ 合并 规则回忆一下, 我们曾在 3. 5. 1 节定义了函数依赖A1A2 …An →B1B2 …Bm它是下面的函数依赖集的缩写:A1A2 …An → B1A1A2 …An → B2…A1A2 …An →Bm也就是说, 我们可以把每个函数依赖右边的属性分解, 从而使其右边只出现一个属性。同样, 我 们也可以把左边相 同的依赖的聚集 用一个 依赖来表 示, 该 依赖的 左边没变, 而右 边则为所有属性组成的一个属性集。两种情形下, 新的依赖集都等价于旧的依赖集。上述著·88·
名的等价关系可用于两个方向。· 我们可以把一个函数依赖 A1 A2 …An → B1B2 …Bm 用一组函数依赖 A1 A2 …An → Bi( i= 1, 2, …, m) 来代替。这种转换称为“分解规则”。· 我们也可以把一组函数依赖 A1 A2 …An → Bi( i= 1, 2, …, m) 用一个依赖 A1 A2 …An→B1B2 …Bm 来代替。这种转换称为“合并规则”。例如, 我们在例 3. 20 中提到了下面这组函数依赖:tit le year → lengthtit le year→filmT ypetit le year→st udioName如何等价于一个依赖title year→length filmT ype studioName也许您会想 象, 和右边 一样, 分解规则 也能应用 于函数 依赖的左 边。然而, 如下例 所示, 并没有左边的分解规则。例 3. 27 考虑例 3. 20 中关系 Movie 的函数依赖之一: title year→length如果我们试图将其左边分解为:title→lengt hyear → length我们得到了两个错误的依赖。也就是说, tit le 并不函数决定 lengt h, 因为可能有两部 电影同名( 比如都叫King Kong) , 但长度不同。同样,year也不函数决定lengt h, 因为任何一年中都必然有长度不同的电影存在。 □3. 6. 2 平凡依 赖对 于函 数依 赖A1A2 …An →B来 说, 如 果B是A中 的某 一 个, 我 们就 称 之为“平 凡的”。例如,tit le year→title就是一个平凡依赖。由于平 凡依赖 意味着“如 果两 个元组 在属 性 A1 , A2 , …, An 上 全都 取值 一致, 则它 们在其中的一个属性上取值一致”, 所以每个关系中的所有平凡依赖都保持恒真。因此, 我们可以假定任何平凡依赖, 而无需依靠数据来检验。在我们最初的函数依赖定义中, 并不允许出现平凡依赖。但是, 鉴于它们恒为真, 而且有时能简化规则的陈述, 把它们包含进来并没有害处。如果允许 平凡依赖, 那么也允许( 作为简化 形式) 依赖右 边的某 些属性同 时出现在 左边。对于函数依赖A1A2 …An →B1B2 …Bm ,· 如果B是A的子集, 则称该依赖为平凡的。· 如果B中至少有一个属性不在A中, 则称该依赖为非平凡的。·98·
· 如果 B 中没有一个属性在 A 中, 则称该依赖为完全非平凡的。因此,title year → year length是非平凡的, 但不是完全非平凡的。如果把右边的year去掉, 我们就得到了一个完全非平凡的依赖。如果函数依赖右边的属性中有一些也出现在左边, 那么 我们 就可 以 将右 边 的这 些 属图 3. 28 平凡依赖规则性删除。也就是说,· 函数依赖 A1A2 …An → B1 B2 …Bm 等价于A1A2 …An →C1C2 …Ck , 其中C是B的子集, 但属性 C 不在 A 中出现。我们 称 这 个 规 则 为“平凡 依 赖 规 则 ”, 如 图3. 28 所示。3. 6. 3 计算属性 的闭包在 引 入 其 他 规则 之 前, 我 们 给 出 一 个 所 有规 则都遵 循的通 用原则。 假设 {A1 ,A2 , …,An }是属性集, S 是函数依赖集。属性集{ A1 , A2 , …,An }在依赖集S下的闭 包是这样 的属性集B, 它使得 满足依赖 集S中 的所 有依赖 的每 个关系也都满足 A1A2 …An → B。也就是说, A1 A2 …An → B 是蕴含于 S 中的函数依赖。我们用{A1 ,A2 , …,An }+来表示属性集A1A2 …An 的闭包。为了简化闭包的计 算, 我们允 许出现平凡依赖, 所以A1 ,A2 , …,An 总在{A1 ,A2 , …,An }+中。图 3. 29 给出了求闭包的过程。从给定的属性集出发, 一旦包含了某函数依赖左边的属性, 就把其右边的属性增加进来, 就这样不断地扩展该集合。最终, 当该集合再也无法扩展时, 得到的结果就是闭包。下面的步骤是求解属性集{ A1 , A2 , …, An }在某函数依赖集下的闭包的算法的更详细描述。1. 设属性集 X 最终将成为闭包。首先, 将 X 初始化为{ A1 , A2 , …, An }。图 3. 29 计算属性集的闭包2. 然后, 重复地搜索某个函数依赖B1B2 …Bm →C,使得所有的 B1 , B2 , …, Bm 都在属 性集 X 中, 但 C 不在其中。于是将C加到属性集X中。3. 根据需要多次重复步骤 2, 直到没有属性 能加到X中。由于X是只增的, 而任何关系的属性数目必然是有限的, 因此最终再也没有属性可以加到 X 中。4. 最 后得到 的不 能再 增加 的 属性 集X就 是{A1 ,A2 , …, An }+的正确值。例 3. 28 让我 们来考虑一个具 有属性A,B,C,D,E , F 的关系。假设该关系有如下的函数依赖: AB → C,·09·
闭包算法为什么是有效的闭包算 法是有 效的, 原因很简 单。利用步 骤 2 的运 算使 每个 属性D都扩 展到X中, 通过多次归纳, 就可以证明, 函数 依赖 A1A2…An→ D 是 成立的( 特殊情况下, 当 D是 A 中的某个属性时, 该依赖是平凡的) 。也就是说, 任何满足 S 中的所有依赖的关系R也满足A1A2 …An →D。归纳基是 0 次迭代。此时D必然是A1 ,A2 , …,An 中的某一个, 并且对于任何关系A1A2 …An → D 必然成立, 因为它是平凡依赖。假设通过归纳 D 在利用依赖 B1 B2 …Bm → D 时加入 X。由归纳假设我们知道, 对于i= 1, 2, …,m,R满足A1A2 …An →Bi。另一方面,R的任何两个在A1 ,A2 , …,An 上全都一致的元组 必然在B1 ,B2 , …,Bm 上也全都 一致。由于R满足B1B2 …Bm →D, 这两个元组在D上也一致。因此,R满足A1A2 …An →D。上面的证明说明闭包算法是有效的; 也就是说, 当把D加入{A1 ,A2 , …,An }+时,函数依赖A1A2 …An →D是成立的。在这里尚未说明的是反过来的情形, 即完备性: 只要 A1 A2 …An → D 成立, D 必将加入{ A1 , A2 , …, An }+。这个证明超出了本书的 范围,在此不予讨论。BC → AD, D → E 和 CF →B。{A, B}的闭包是什么, 即{A, B}+是什么?我们从 X = {A, B}出发。首先, 我们注意到, 函数依赖 AB → C 左边的所有属 性都在X中, 所以我们可以把该依 赖右边的属性C加入到X中。因此, 步骤 2 的第一次 迭代后,X变成了{A,B,C}。然后, 我们看到 BC → AD 的左边现在都包含在 X 中, 因而可以把属性 A 和 D 加入到 X 中。①A 已经在 X 中了, 但 D 不在其中, 所以, X 又变成了{A, B, C, D}。这时, 我们可以根据函数依赖 D → E 把 E 加入到 X 中, 现在 X 就成了{A, B, C, D, E }。X 的扩展到此为止。特别值得一提的是, 函数依赖 CF → B 没用上, 因为它的左边不包含在 X 中。因此,{A, B}+= {A, B, C, D, E }。如果我们知道如何计算任意属性集的闭包的话, 就能 检验给定的任一函 数依赖 A1A2…An → B 是否蕴含于 依赖 集 S。 首先 利用 依赖 集 S 计算 { A1 , A2 , …, An }+。如 果 B 在{A1 ,A2 , …,An }+中, 则A1A2 …An →B的确 蕴含 于S; 而 如 果B不在 {A1 ,A2 , …,An }+中, 则该依赖并不蕴含于 S 。更概括地说, 右边为属性集的依赖也可以检验, 只要我们记得它是依赖集 的简化形式就有 办法了。于是, 当且仅当所有的B1 ,B2 , …,Bm 都在 {A1 ,A2 ,…, An }+中, A1 A2 …An → B1 B2 …Bm 就蕴含于依赖集 S 。 ① : 回 忆 一下BC→AD是 一对 依赖BC→A和BC→D的 简化 形式 。愿 意的 话, 我 们也 可 以分 别 处理 这 两个依赖。·19·
闭包和键码注意, 当且仅当A1 ,A2 , …,An 是所考虑的关系的超键码时, {A1 ,A2 , …,An }+才是所有属性的集合。因为只有这时,A1 ,A2 , …,An 函数决定所有其他的属性。A1 ,A2 , …,An 是否是一个关 系的键码, 可以这样检验, 首先检查{A1 ,A2 , …,An }+, 它应该 包含所有的 属性, 然后检查 从{A1 , A2 , …, An }中删除任 何一个 属性 构成 的集合 S , S+将 不包含所有的属性。例 3. 29 考虑例 3. 28 中的关系和函数依赖。假设我们想要检验AB→D是否蕴含于这些函数依赖。在上例中我们计算了{A,B}+, 为{A,B,C,D,E}。由于D是后一个集合的成员, 因此我们可以断定AB→D的确蕴含于这些函数依赖。另一方面, 考虑函数依赖D→A。为了检验该依赖关系是否蕴含于给定的函数依赖,首先计算{D}+。为此, 我们从 X = {D}开始计算, 利用依赖 D → E 可以把 E 加入到集合X 中, 但 X 再也 不能 扩展 了, 我 们无 法找 到其 左 边包 含 在 X 中 的 任 何其 他 依赖, 所以,{D}+= {D, E }。由于 A 不是{D, E }中的成员, 因此, 我们可以断定 D → A 并不蕴含于给定的函数依赖。3. 6. 4 传递规 则传递规则使我们级联两个函数依赖。· 如果A1A2 …An →B1B2 …Bm 和B1B2 …Bm →C1C2 …Ck 在 关系R中 成立, 则A1A2…An → C1 C2 …Ck 在 R 中也成立。如果某个 C 在 A 中, 我们可以利用平凡依赖规则将其从右边删除。为了 说明 传递 规则为 何成 立, 我们 采 用 3. 6. 3 节的 检验 过 程。要检 验A1A2 …An →C1C2 …Ck 是否成立, 就需要计算闭包{A1 ,A2 , …,An }+。函数依 赖A1A2 …An →B1B2 …Bm 告诉 我们,B1 ,B2 , …,Bm 全 都在{A1 ,A2 , …,An }+中。然后, 我们可以利用依 赖 B1 B2 …Bm → C1 C2 …Ck 把 C1 , C2 , …, Ck 加入 到{ A1 , A2 , …,An }+中。因为所有的C都在{A1 ,A2 , …,An }+中, 所以我们可以断定A1 A2 …An → C1 C2 …Ck对于任何满足 A1 A2 …An → B1B2 …Bm 和 B1B2 …Bm → C1 C2 …Ck 的关系都成立。例 3. 30 我们从图 3. 12 中的关系 Movie 开始, 该关系是在 3. 2. 4 节建立的, 用 来表示类Movie的 四个属性 以及它 与类Studio之间 的联系ownedBy。 该关系及 一些实例 数据为:title year length filmType studioNameStar Wars1977 124 color FoxMighty Ducks 1991 104 color DisneyWayne' s World1992 95 color Paramount·29·
假设我们决定在这个关系中表示有关电影所属制片公司的某些数据。为了简化起见,我们将只增加该制片公司所在的城市, 表示其地址。于是, 该关系就变成了这样:Title year length filmType studioName studioAddrStar Wars1977 124 }color Fox HollywoodMighty Ducks 1991 104 }color Disney Buena VistaWayne' s World 1992 95 Ocolor Paramount Hollywood 我们有理由宣称如下两个依赖成立:tit le year→st udioNamestudioName → studioAddr第一个依赖 是成立的, 因为 类 Movie 的 联系 ownedBy 是单 值的; 一部电 影只属于 一个制片公司。第二个依赖是成立的, 因为在类 St udio 中, 属性 address 是单值的; 它属于字符串类型( 见图 2. 6) 。传递规则使我们可以把上面两个依赖组合起来, 得到一个新的依赖:tit le year → studioAddr该依赖意味着名称和年份( 即一部电影) 决定一个地址—— 拥有该电影所有权的制片公司地址。 □3. 6. 5 函数依 赖的闭包正如我们看到 的那样, 给 定一组函数 依赖, 通常可 以推导出 某些其 他依赖, 既包括 平凡的也包括非平凡的依赖。在后续的几节中, 我们将区分“给定的”依赖—— 即最初说明的某个关系所满足的依赖—— 和“推导的”依赖—— 即利用本节介绍的规则之一或利用属性闭包算法推导得出的依赖。此外, 有时我们需要选择用一个关系的哪些依赖来表示它的所有依赖集。任何一个能从中导出关系的所有依赖的给定依赖集称为该关系的一个“基”。如果一个基的任何真子集都不能推导出该关系的依赖全集, 则称此基为“最小的”。例 3. 31 考虑关系 R( A, B, C) , 其中, 每个属性都函数决定其他两个属性。因此推导的依赖全集包括六个左、右各有一个属性的依赖; 即A→B,A→C,B→A,B→C,C→A 和 C → B。它还包括三个左边 有两个属性的非 平凡依赖: AB → C, AC → B 和 BC →A。还有成对依赖的简化形式, 如 A → BC, 也可能包含平凡依赖, 如 A → A, 或者不是完全非平 凡的依 赖, 如 AB → BC( 虽然在 函数依 赖的严 格定 义里, 并不 要求 我们列 出平 凡或部分平凡的依赖, 或者右边包含多个属性的依赖) 。上面的关系和它的依赖有几个最小基。一个是{ A → B, B → A, B → C, C → B }另一个是{ A → B, B → C, C →A }上面的关系还有许多其他的基, 甚至是最小基, 列举这些基, 留作练习。·39·
推导函数依赖全集的规则如果我们想知道一个函数依 赖是否蕴含于某 些给定的依赖, 3. 6. 3 节的闭包计算能解决这个问题。不过, 有趣的是, 存在一组叫做“阿姆斯特朗( Armstrong) 公理”的规则, 通过它可以推导出蕴含于给定依赖集的任何函数依赖。这些公理是:自反律。如果{ B1 , B2 , …, Bm } { A1 , A2 , …, An }, 则 A1A2 …An → B1 B2 …Bm 。这些依赖都是平凡依赖。增长律。如果A1A2 …An →B1B2 …Bm , 则对于任何属性集C1 ,C2 , …,Ck ,A1A2 …AnC1 C2 …Ck → B1B2 …Bm C1C2 …Ck 。传递律。如果 A1 A2 …An → B1 B2 …Bm 而且 B1B2 …Bm → C1C2 …Ck , 则 A1 A2 …An →C1C2 …Ck 。3. 6. 6 本节练 习* 练习 3. 6. 1: 考虑关系模式为 R( A, B, C, D) 的关系和函数 依赖 AB → C, C → D 和 D→ A。(a) 求蕴含于给定函数依赖的所有非平凡的函数依赖。( b) 求 R 的所有键码。(c) 求R的所有超键码( 不包括键码) 。练习 3. 6. 2: 对下列关系模式和函数依赖, 按照练习 3. 6. 1 要求求解:(i)S(A,B,C,D) , 函数依赖为A→B,B→C和B→D。(ii)T(A,B,C,D) , 函数依赖为AB→C,BC→D,CD→A和AD→B。( iii) U( A, B, C, D) , 函数依赖为 A → B, B → C, C → D 和 D → A。练习 3. 6. 3: 用 3. 6. 3 节的闭包检验法证明下列规则成立:* (a) F左边增长。如果A1A2 …An →B是一 个函数依赖,C是 另一个属性, 则A1A2 …AnC→ B 也成立。 (b) N全增长。如果A1A2 …An →B是一个函数依赖,C是另一个属性, 则A1A2 …AnC→ BC 也成立。注意: 从这一规则出发, 3. 6. 5 节方框中提到的“阿姆斯特朗”( Armstrong) 公理”的“增长”规则就很容易证明了。 (c) B伪传递。假设依赖A1A2 …An →B1B2 …Bm 和C1C2 …Ck →D成立, 而且每个B都在C中, 则A1A2 …AnE1E2 …Ej →D成立, 其中,E是B中没有出现的C的所有属性。 ( d) L加法。如果函数依赖 A1 A2 …An → B1 B2 …Bm 和 C1 C2 …Ck → D1D2 …Dj 成立, 则函数依赖A1A2 …AnC1C2 …Ck→B1B2 …BmD1D2 …Dj也成立。上式中我们应该把同时出现在A和C或同时出现在B和D中的属性删除一份副本( 即一个属性在依赖的同一侧只能出现一次) 。! 练习 3. 6. 4: 请 给出满足 给定依赖、但 不满足假 设的推导 依赖的 关系实例, 从而 证明 下·49·
6. 5. 5 本节练习 253……………………………………………………………………………6. 6 SQL3 中的触发程序 254…………………………………………………………………………6. 6. 1 触发和约束 254…………………………………………………………………………6. 6. 2 SQL3 触发程序 254………………………………………………………………………6. 6. 3 SQL3 的断言 257…………………………………………………………………………6. 6. 4 本节练习 258……………………………………………………………………………6. 7 本章总结 259………………………………………………………………………………………6. 8 本章参考文献 260…………………………………………………………………………………第 7 章 SQL系统概况 261………………………………………………………………………………7. 1 编程环境中的 SQL 261…………………………………………………………………………7. 1. 1 匹配失衡问题 262………………………………………………………………………7. 1. 2 SQL/ 宿主语言接口 262…………………………………………………………………7. 1. 3 说明( DECLARE) 段 263…………………………………………………………………7. 1. 4 使用共享变量 263………………………………………………………………………7. 1. 5 单行查询语句 264………………………………………………………………………7. 1. 6 游标 265…………………………………………………………………………………7. 1. 7 通过游标的更新 267……………………………………………………………………7. 1. 8 游标选项 267……………………………………………………………………………7. 1. 9 为取出的元组排序 268…………………………………………………………………7. 1. 10 防止并发更新的保护措施 269…………………………………………………………7. 1. 11 滚动游标 269……………………………………………………………………………7. 1. 12 动态 SQL 270…………………………………………………………………………7. 1. 13 本节练习 271……………………………………………………………………………7. 2 SQL中的事务 273…………………………………………………………………………………7. 2. 1 可串行性 273……………………………………………………………………………7. 2. 2 原子性 275………………………………………………………………………………7. 2. 3 事务 276…………………………………………………………………………………7. 2. 4 只读事务 277……………………………………………………………………………7. 2. 5 读脏数据 278……………………………………………………………………………7. 2. 6 其他隔离性级别 279……………………………………………………………………7. 2. 7 本节练习 280……………………………………………………………………………7. 3 SQL 环境 281………………………………………………………………………………………7. 3. 1 环境 281…………………………………………………………………………………7. 3. 2 模式 282…………………………………………………………………………………7. 3. 3 目录 283…………………………………………………………………………………7. 3. 4 SQL 环境中的客户程序和服务程序 284………………………………………………7. 3. 5 连接 284…………………………………………………………………………………7. 3. 6 会话 285…………………………………………………………………………………7. 3. 7 模块 285…………………………………………………………………………………7. 4 SQL2 的安全和用户授权 286……………………………………………………………………7. 4. 1 权限 286…………………………………………………………………………………·Ⅹ·
面给出的规则不是合法的函数依赖规则。* ( a) 如果 A → B, 则 B → A。( b) 如果 AB → C, 且 A → C, 则 B → C。( c) 如果 AB → C, 则 A → C, 或 B → C。! 练习 3. 6. 5: 证明如果一个关系没有一个属性能由所有其他属性函数决 定, 则该关 系根本不存在非平凡的函数依赖。! 练习 3. 6. 6: 设X和Y是属性集。证明如果XY, 则X+Y+, 其中闭包是针对同一函数依赖集而言的。! 练习 3. 6. 7: 证明(X+)+=X+。! 练习 3. 6. 8: 如 果X+=X, 则 称属性 集X是封闭的 ( 相 对于给定 的函数依 赖集而 言) 。考虑一个关系模式为 R( A, B, C, D) 的关系和一个未知的函数依赖集。如果已知某些属性集是封闭的, 我们就能找出相应的函数依赖集。给出下列条件所对应的函数依赖:* ( a) 四个属性的所有集合都是封闭的。( b) 仅有的闭包是和{A, B, C, D}。( c) 闭包是、{A, B}和{A, B, C, D}。! 练习 3. 6. 9: 找出例 3. 31 中的依赖和关系的所有最小基。! ! 练习 3. 6. 10: 试说明: 如果一个 函数依赖 F 蕴含于 某些给定 的依赖, 则我 们可以从 给定的依赖出发, 利用 Armst rong 公理( 定义在 3. 6. 5 节方框内) 证明 F 成立。提示: 检验计算属性 集闭包的 算法, 说明 如何 利用 Armstrong 公理 推导 出某些 函数 依赖, 来模 拟该 算法的每个步骤。3. 7 关系数据库模式设计我们已经几次注意到从面向对象的ODL设计出发( 以及在少数情况下, 从E/R设计出发) 直接向关系数据库模式转换, 会导致关系数据库模式上的一些问题。我们发现主要的问题是冗余性, 即一个事实在多个元组中重复。而且, 我们发现造成这种冗余的最常见的原因是企图把一个对象的单值和多值特性包含在一个关系中。例如, 在 3. 2. 2 节, 当我们企 图把电影的单值 信息( 如长 度) 和多 值特性( 如 影星集) 存储 在一起 的时候, 就导致 了信息冗余。出现的问题见图 3. 27, 现在把它复制在图 3. 30 中。在那一节, 当我们试图存储一个影星的单值出生日期信息和一组地址信息时, 就发现了类似的冗余现象。在本节, 我们将按如下步骤解决好的关系模式设计可能遇到的问题:1. 首先, 我们应该更详细地分析关系模式有缺陷时引起的问题。2. 然后, 我们引入“分解”的思想, 把一个关系模式( 属性集) 分解成两个更小的模式。3. 其次, 我们引入“Boyce-Codd 范式”( Boyce-Codd Normal Form) , 即“BCNF ”, 给出一个消除这些问题的关系模式应满足的条件。4. 在 我 们 解 释 如 何 通过 分 解 关 系 模 式 保 证 满 足 BCNF 条件 时, 这 几 点 是 互 相 联系的。·59·
title year length filmType studioName starNameStar Wars 1977 124 color Fox Carr ie FisherStar Wars1977 124 color Fox Mark HamillStar Wars1977 124 color Fox Harrison FordMighty Ducks 1991 104 color Disney Emilio EstevezWayne' s World1992 95 color Paramount Dana CarveyWayne' s World1992 95 color Paramount Mike Meyers图 3. 30 出现异常的关系Movie3. 7. 1 异常当我们企图把太多的信息存放在一个关系里时, 出现的诸如冗余之类的问题称为“异常”(anomaly) 。我们遇到的主要的几种异常是:1. 冗 余。信息 可能 在 多个 元组 中不 必要 地 重复。如 图 3. 30 中 电影 的长 度和 电 影类型。2. 修改异常。我们可能修改了一个元组中的信息, 但另一个元 组中的相同的信 息却没有修改。例如, 如果我们发现星球大战( Star Wars) 的实际长度为 125 分钟, 我们可能比较粗心, 只修改了图 3. 30 中的第一个元组, 而没有修改第二和第三个元组。当然, 也许有人会争论, 不应该如此粗心。但我们将会看到, 重新设计关系 Movie 从而避免出现这种错误是可能的。3. 删除异常。如果一组属性的值变为空, 带来的副作用可能是丢失一些其他信息。例如, 如 果我们从 Might y Ducks 的 影星集中删除了 Emilio Estevez, 那 么, 数据 库里就没 有这部电影的影星信息了。关系Movie中的最后一个Mighty Ducks元组将会消失, 同时消失的还有它的长度( 104 分钟, 原文误为 95 分钟) 和类型( 彩色) 信息。3. 7. 2 关系分 解消除这 些异常的一个可 行的方法是“分解”关系。R 的分解 包括把 R 的属性 分开, 以构成两个新的关系模式。我们的分解规则也包括通过对 R 的元组进行投影而增加新关系的方法。描述了分解过程之后, 我们将说明如何选择一个能消除异常的分解方法。给定一个模式为{A1 ,A2 , …,An }的关系R, 我们可以把R分解为两个关系S和T, 模式分别为{B1 ,B2 , …,Bm }和{C1 ,C2 , …,Ck }, 使得:1. {A1 ,A2 , …,An } = {B1 ,B2 , …,Bm } ∪ {C1 ,C2 , …,Ck }。2. 关系S中的元组是R的所有元组在{B1 ,B2 , …,Bm }上的投影。也就是说, 对于R的当前实例中的每个元组t, 取t在属性B1 ,B2 , …,Bm 上的分量。这些分量构成了一个元组, 它属于S的当前实例。然而, 关系是集合, 而R中的两个不同元组投影到S上, 可能导致 S 中的相同 元组。如果出现了 这种情况, 我们在 S 的当前实例中 只为每个元组保 留一份副本。3. 类似地, 关系T中的元组是R的当前实例中的元组在属性集{C1 ,C2 , …,Ck }上的·69·
投影。例 3. 32 让我们来分解图 3. 30 中的关系Movie。首先进行模式分解。我们选择下面两个关系, 在 3. 7. 3 节将会看到这种选择的优点:1. 一个关系称为 Movie1, 其模式是除了 starName 以外的所有属性。2. 另一个关系称为Movie2, 其模式包括属性title,year和st arName。现在, 我们通过分解图 3. 30 中的采样数据来说明分解关系实例的过程。首先, 我们构造在Movie1 模式上的投影:{tit le,year,length,filmT ype,st udioName}图 3. 30 中的前三个元组在这五个属性上的分量都相同, 是:{St ar Wars, 1977, 124,color,Fox}第四个元组的 前五个分量构成 一个不同的元 组, 而 第五和第 六个元 组构成相 同的五分 量元组。结果得到的关系 Movie1 如图 3. 31 所示。title year length filmT ype studioNameStar Wars 1977 124 color FoxMighty Ducks1991 104 color DisneyWayne' s World1992 95 color Paramount图 3. 31 关系 Movie1其次, 考 虑图 3. 30 在Movie2 模 式上 的投影。 图中的 六个 元组 在属 性tit le,year和starName上至少有一个分量不同, 所以关系 Movie2 的结果如图 3. 32 所示。 □title year starNameStar Wars1977 MCarr ie FisherStar Wars 1977 MMark HamillStar Wars1977 MHarrison FordMighty Ducks1991 MEmilio EstevezWayne' s World 1992 MDana CarveyWayne' s World1992 MMike Meyers图 3. 32 关系 Movie2注意, 这种分解如何消除了 3. 7. 1 节提到的异常。冗余已经消除了; 比如, 每部电影的长度只在关系 Movie1 中出现一次。修改异常的危险也不存在了。例如, 因为我们只需要在Movie1 的一个元组中修改St ar Wars的长度信息, 所以该电影就不可能出现两个不同的长度。最后, 删除异常的危险也不复存在。如果我们删除了电影Might y Ducks中的全部影星, 也就是说, 这个删除操作使 得该电影在 Movie2 中消失了。但 该电影的其 他信息依 然可以在Movie1 中找到。由于一部电影的名称和年份可能出现多次, 看起来Movie2 可能还存在冗余信息。然而, 这 两 个 属 性 构 成 了 电 影 的 键 码, 而 且 没 有 更 简 洁 的 方 法 来 表 示 一 部 电 影。 此 外,Movie2 并 没有 提供出 现修 改异 常 的机 会。我们 也 许会 这样 假设: 如 果 我们 改变 了St ar·79·
Wars 的 Carrie Fisher 元组 中的 年 份, 而 另外 两 个 元组 保 持不 变, 于 是 就出 现 了修 改 异常。然而, 在我们假定的函数依赖中并不排除 1997 年存在另一部名为Star Wars的电影,而 Carrie Fisher 也可能担任该电影的主演。因此, 我们不要去阻止修改一个 St ar Wars 元组的年份, 而且这种修改也未必就是错误的。3. 7. 3 BC 范式分解的目的在于用几个不会导致异常的关系来代替原来的关系。已经证明, 存在一个简单的条件, 在这个条件下, 能保证避免上 面所讨论的异常。 该条件称为“Boyce-Codd范式”, 或BCNF。· 当且仅当: 只要关系R有非平凡依赖A1A2 …An →B, {A1 ,A2 , …,An }必然是R的超键码; 满足该条件的关系R就属于BCNF。也就是说, 每个非平凡函数依赖的左边必须是超键码。回忆一下, 超键码不必是最小的。因此,BCNF条件的一个等价陈述是: 每个非平凡函数依赖的左边必须包含键码。当我们发现一 个违背 BCNF 的 依赖时, 找到具有相同左 边的所有其他依 赖往往是 有用的, 无论它们是否违背BCNF。下面给出BCNF的另一种定义, 我们按这种定义查找具有相同左边的依赖集, 判断其中是否至少有一个违背 BCNF 条件的非平凡依赖。· 当且仅当: 只要非平凡依赖A1A2 …An →B1B2 …Bm 在关系R中成立, {A1 ,A2 , …,An }必然是 R 的超键码; 满足该条件的关系 R 就属于 BCNF 。这个要求与原始的 BCNF 条件是等价的。回忆一下, 依赖 A1 A2 …An → B1 B2 …Bm 是依赖集A1A2 …An →Bi( 其中i= 1, 2, …,m) 的另一种形式。既然至少有一个Bi 不在A中( 否则A1A2 …An →B1B2 …Bm 就 是平凡 依赖 ) , 就可 以根 据原始 定义 来判 断A1A2 …An →Bi 是否违背BCNF。例 3. 33 图 3. 30 中的关系Movie不属于BCNF。为了说明原因, 我们首先需要确定哪些属性是键码。在例 3. 21 中我们曾讨论过为什么{title, year, starName}是键码。因此,任何包含这三个属性的属性集都是超键码。我们在例 3. 21 中采用的讨论方法也可以用来解 释 为 什么 不 完全 包 含 这 三 个属 性 的 属 性 集不 可 能 是 超 键码。 因 此, 我 们 断 言{tit le,year, starName}是 Movie 的唯一的键码。然而, 考虑函数依赖:title year → length filmT ype studioName我们知道它在Movie中是成立的。回忆一下我们断言这个依赖成立的理由: 最初的ODL设计中有键码{ title, year }, 单值属性 length 和 filmT ype, 以及指向所属的制片公司的单值联系ownedBy。遗憾的是, 上述依赖的左边不是超键码。特别是, 我们知道 tit le 和 year 并不函数决定第六个属性starName。因此, 这个依赖的存在违背了BCNF条件, 并告诉我们Movie不属于 BCNF 。而且, 根据 BCNF 的原始定义, 右边只需要一个属性, 我们可以把三个函数依赖中的任何一个, 比如tit le year→length, 作为违背BCNF的例子。 □例 3. 34 另一方面, 图 3. 31 中的 Movie1 属于 BCNF 。因为title year→length filmT ype studioName·89·
在该关 系中成立, 并且 我们曾 经讨论过, title 或 year 本 身都不 能单独 地函 数决定 其他 任何属性,Movie1 的唯一的键码是{tit le,year}。而且,Movie1 仅有的非平凡函数依赖的左边必定至少包含 title 和 year, 所以它们的左边必然是超键码。因此, Movie1 属于 BCNF。□例 3. 35 我们断言任 何双属性关系都 属于 BCNF。 为此, 需要 检验右边有一个 属性的可能非平凡依赖。因为需要考虑的情形并不多, 让我们逐个来讨论。在下面的讨论中,假设这两个属性是 A 和 B。1. 没有非平凡函数依赖。当然BCNF条件必定满足, 因为只有非平凡依赖才可能违背 BCNF 条件。顺便提一句, 这种情形下, {A, B}是唯一的键码。2.A→B成立, 但B→A不成立。这种情形下,A是唯一的键码, 而且任何非平凡依赖的左边必定包含 A( 事实上, 左边只能是 A) 。所以, 没有违背 BCNF 条件。3.B→A成立, 但A→B不成立。这种情形与情形 2 对称。4. A → B 和 B → A 都成立。则 A 和 B 都是键码。当然任何依赖的左边都至少包含其中一个键码, 所以也没有违背BCNF条件。值得注意的是情形 4, 这种情况下, 一个关系可能有多个键码。 而 BCNF 条件只 要求某个键码包含在任何非平凡依赖的左边, 并不要求所有键码都包含在左边。还应注意到有两个属性的关系, 每个都函数决定另一个, 这不是完全不可思议的。比如, 一个公司可能给每个雇员分配唯一的雇员号, 同时记录他们的社会保险号。拥有这两个属性 ——empID和ssNo 的关系中, 每个属性都函数决定另一个属性。另一方面, 每个属性都 是键码, 因 为我们不希望看到两个元组在任一属性上取值相同。 □3. 7. 4 分解成 BCNF通过不断地选择合适的分解, 我们可以把任何关系模式分解成其属性子集的聚集, 而这些子集具有如下重要特性:1. 这些子集都是属于 BCNF 的关系模式。2. 在某种 意义上( 3. 7. 6 节将给 出确切含义) , 分解 后关系中的 数据如实 地表示原 始关系中的数据。大体上讲, 我们要有能力从分解的关系准确地重构原始的关系。例 3. 35 也许给人一种印象, 我们要做的一切就是把一个关系模式分解为双属性的子集, 结果必然属于 BCNF 。但我们在 3. 7. 6 节将会看到, 这种随意的分解并不满足条件 2。图 3. 33 基于BCNF违例的关系模式分解事实 上, 我们 必 须更 谨 慎些, 并利 用 违背BCNF的 函数 依赖来指导分解过程。我们 将 要采 取 的分 解 策 略是 寻 找一 个 违背BCNF的非 平凡 依 赖 A1A2 … An → B1 B2 …Bm ; 也 就 是 说, { A1 , A2 ,…,An }不是超键 码。作为试 探, 我 们通常 在右边 加入 尽量多的由{ A1 , A2 , …, An }函数决定的属性。图 3. 33 说明如何把一个关系中的属性分为两个重叠的关系模式。一个模式包含了违背 BCNF 的依赖中的 所有属性, 而另一个模式 则包含了该依赖的 左边以及未 包含在该依赖中的所有属性, 也就是, 除了A以外, 再加上B之外的所有属性。·99·
例 3. 36 考 虑我们的不断滚 动的实例, 图 3. 30 中的关系 Movie。我 们在例 3. 33 中看到,title year→length filmT ype studioName是一个 BCNF 的违例。在这个实例中, 依赖的右边已经包含了由 title 和 year 函数决定的所有属性, 因此, 我们用这个BCNF违例把Movie分解成:1. 包含该依赖所有属性的模式, 即:{title,year,length,filmT ype,studioName}2. 包 含 除 了 该 依 赖 右 边 的 三 个 属 性 之 外 的 所 有 Movie 属 性 的 模 式。 因 此, 除 去lengt h, filmT ype 和 studioName, 得到了第二个模式:{title,year,starName}注意, 这些模式就是例 3. 32 中选择的关系 Movie1 和 Movie2 的模式。我们在例 3. 34中已经看到, 它们都属于BCNF。 □例 3. 37 让我们考虑例 3. 30 中介绍的关系 MovieStudio。该关系存放了关于电影以及它们所属的制片公司和这些制片公司的地址等信息。其关系模式和一些典型的元组如图 3. 34 所示。title year length filmType studioName studioAddrStar Wars1977 e124 color Fox HollywoodMighty Ducks1991 e104 color Disney Buena VistaWayne's Wor ld 1992 e95 }color Paramount HollywoodAddams Family1991 e102 color Paramount Hollywood图 3. 34 关系 MovieStudio注意,MovieStudio包 含 冗 余 信 息。 由 于 我 们 在 常 见 的 实 例 数 据 中 增 加 了 属 于Paramount( 派拉蒙) 的第二部电影, 因此 Paramount 的 地址出现了两次。 然而, 产生 这个问题的原因与例 3. 36 有所不同。后者是由于多值联系( 给定电影中的影星) 和该电影的其他信息存放在一起。而这个例子中, 所有的属性都是单值的: 表示电影长度的属性 lengt h,把该电影与其 所属的唯一的制 片公司相连的联 系ownedBy, 以及 表示制片公 司地址的 属性address。在 这种 情 况下, 存在 的 问题 是“传递 依 赖”。 正如 我 们 在例 3. 30 中提 到 的, 即 关 系MovieSt udio有如下两个依赖:tit le year→st udioNamestudioName→st udioAddr我们可以利用传递规则, 得到一个新的依赖tit le year → studioAddr也就是说,tit le和year( 即电影的键码) 函数决 定了制片 公司的 地址—— 拥有 该电影的 制片公司的地址。由于tit le year → length filmT ype是另一个明显的依赖, 我们断定{title,year}是MovieStudio的键码; 事实上, 它是唯一的·001·
键码。另一方面, 在上述传递规则的应用中所用到的两个依赖之一studioName→st udioAddr是非平凡的, 但它 的左边并不是超键 码。这个事实告诉 我们MovieSt udio不属于BCNF。利用上面的依赖, 我们可以通过分解规则来解决这个问题。分解后的第一个模式是该依赖自己的属性, 即:{studioName,studioAddr}第二个模 式是除了 studioAddr( 因为它是分解 所用的依赖右边 的属性) 之外 MovieStudio的所有属性。因此, 另一个模式就是:{title,year,length,filmT ype,studioName}把 图 3. 34 在 这 两 个 模 式 上 进 行 投 影, 就 得 到 了 图 3. 35 和 图 3. 36 所 示 的 关 系MovieSt udio1和MovieStudio2。它们都属于BCNF。MovieStudio1 的唯一键码是{tit le,year }, 而 MovieStudio2 的唯一键码是{studioName}。在每种情形下, 都没有左边不包含键码的非平凡依赖。 □title year length filmType studioNameStar Wars 1977 124 color FoxMighty Ducks1991 104 color DisneyWayne's World 1992 95 color Par amountAddams Family 1991 102 color Paramount图 3. 35 关系MovieStudio1studioName studioAddrFox HollywoodDisney Buena VistaParamount Hollywood图 3. 36 关系MovieStudio2在前面 的每 个例子 中, 正确 地应用 分解 规则就 足以 产生 属于BCNF的 关系的 聚集。而一般来说, 情况并不总是这样。例 3. 38 我们把例 3. 37 中的函数依赖扩 展成多于两个的函 数依赖链。考虑具 有如下模式的关系{title, year, studioName, president, presAddr}也就是说, 该关系的每个元组都包含了一部电影及其制片公司、该制片公司的总裁以及该制片公司总裁的地址等信息。我们假定该关系的三个函数依赖如下:title year → studioNamestudioName→presidentpresident → presAddr该关系的唯一键码是{title,year}。所以, 上面的后两个依赖就违背了BCNF。我们可·101·
以从studioName→president开始 分解。首 先, 我们 应该在 该函 数依赖 的右 边增 加 studioName 闭包中 的 任何 其他 属性。把传递规则用于依赖studioName→president和president→presAddr, 我们得到:st udioName → presAddr然后把左边同为studioName的两个依赖组合起来, 就得到了st udioName → president presAddr该函数依赖的 右边已经最大限 度地扩展了, 现在我 们就可以 把关系 模式分解 为下面两 个模式:{tit le,year,st udioName}{ studioName, president , presAddr}其中, 第一个模式属于BCNF。而第二个模式唯一的键码是{st udioName}, 但却拥有一个违背 BCNF 的依赖:president→presAddr因此, 我们必须利用该 依赖再次进行分 解。最终我们得到了 都属于 BCNF 的 三个关系 模式, 它们是:{title, year , st udioName} f{studioName,president}{ president , presAddr} □通常, 我们 必须根 据实 际需 要多次 应用 分解规 则, 直到 所有的 关系 都属 于BCNF为止。我们可以肯定最终必然成功, 因为每次对关系 R 应用分解规则时, 得到的两个关系模式的属性都比R少。 就像我们在例 3. 35 中看 到的, 当分 解到只有两个属 性的关系时, 它必然属于 BCNF。为了说明分解为什么总是得到更小的模式, 假设我们有一个BCNF的违例A1A2 …An→ B1 B2 …Bm 。我们可以假定该依赖已经是扩展后的, B 中包含了由 A 函数决定的所有属性, 而且, 任何既包含在A中, 又包含在B中的属性已经从B中删除。分解后的一个模式是除了 B 以外的 R 的所有属性。B 中必定至少有 一个属性, 因此该模式并不会包含所有的属性。另一个模式是 A 的所有属性和 B 的属性。这个集合不可能是 R 的属性全集, 因为, 如果是这样的话, 则{A1 ,A2 , …,An }就是R的超键码( 也就是说,A的 属性能函数决定R的所有其他属性) 。左边为超键码的依赖不是 BCNF 的违例。因此, 可以断言分解后的两个模式都比R的模式小。所以, 不断分解的过程必然最终导致 BCNF 关系的聚集。3. 7. 5 函数依 赖的投影在分解关系 模式时, 我 们需要检查得到 的模式是否属于BCNF。正如 我们在例 3. 38中看到的, 新模式本身可能有一个甚至两个都包含 BCNF 的违例。然而, 如果我们不能确定一个关系中成立的函数依赖, 那么怎样才能判断该关系是 否属于BCNF呢? 在例 3. 38·201·
中, 我们用一种特定的方法推导了新关系中成立的函数依赖。幸运的是, 有一种系统的方法可以找到分解得到的关系中成立的函数依赖。假设把关系 R 分解为关系 S 和另一个关系。设 F 是已知的 R 中成立的依赖集。为了计算S中成立的函数依赖, 请按照下面的步骤进行:考虑包含于 S 的属性集的每个属性集 X。计算 X+。于是对于满足下列条件的每个属性B, 函数依赖X→B在S中成立:1. B 是 S 的一个属性,2.B属于X+, 而且3. B 不属于 X。例 3. 39 设R的模式为R(A,B,C,D) ,R中给定的函 数依赖为A→B和B→C。设 S( A, C) 是 R 经过某种分解得到的一个关系。我们来计算 S 中成立的函数依赖。原则上, 我们必须计算S的属性集{A,C}的每个子集的闭包。我们从{A}+开始。很容易就能看出, 该集合为{A, B, C}。由于 B 不在 S 的模式中, 我们并不认为 A → B 是 S 的一个依赖。然而,C在S的模式中, 所以我们断言依赖A→C在S中成立。现在, 我们必须考虑{C}+。因为 C 不在给定依赖的左边, 它的闭包不包含任何新的属性, 于是{C}+= {C}。通常, 一个属性集 中如果不包含任 何给定依赖的左 边, 它就不 能导出 S 的任何依赖。我 们还必须考 虑{A,C}+, 即{A,B,C}。由于除 了在考虑 {A}+时已经得 到的依赖 之外, 这个属性集没有引入任何新的依赖, 因此, 结论是 A → C 是我们需要为 S 声明的唯一依赖。当然,S还包含从该依赖导出的其他依赖, 比如AC→C或平凡依赖A→A。不过,它们可以由 3. 6 节给出的规则推导出来, 在我们给出 S 的函数依赖时不必特别指明。 □例 3. 40 现在考虑R(A,B,C,D,E) 分解为S(A,B,C) 和另一个关系。设R的函数依赖为 A → D、B → E 和 DE → C。首先, 考虑{A}+= {A,D}。由于D不在S的模式中, 这个属性集没有引入任何依赖。同样, {B}+= {B, E }和{C}+= {C}也没有为 S 引入函数依赖。现在我们来考虑双属性子集。{A,B}+= {A,B,C,D,E}。因此, 我们得到了S的依赖 AB → C。其他双属性子集都没有为 S 提供依赖。当然, S 的所有三个属性的集合{A,B,C}, 也 不 能 为S提 供 新 的 非 平 凡 依 赖。 所 以, 我 们 需 要 为S声 明 的 唯 一 依 赖 就 是AB → C。 □3. 7. 6 从分解 中恢复信 息现在, 把我们的注意力转移到这个问题上: 为什么 3. 7. 4 节的分解算法能保留原始关系中包含的信息? 其思想在于, 如果我们采用该算法, 则原始关系的元组的投影可以重新“连接”起来, 产生原始关系的所有元组并且只产生原始关系的元组。为了 把情况简 化, 我 们考虑 关系 R, 其模 式为{A, B, C}, 函 数依赖 为 B → C, 并假 定该依赖是BCNF的违例。像例 3. 37 中的情况是可能出现的, 即还有另一个函数依赖A→B, 形成一个传递依赖链。在这种情形下, {A}是唯一的键码, 而 B → C 的左边显然不是超键码。另一种可能性是B→C是 唯 一 的 非 平 凡 依 赖 , 在 这 种 情 形 下, {A,B}是唯一的·301·
简化搜索依赖的过程当我们 利用 3. 7. 5 节的算 法从R的函数依 赖推导 关系S的 依赖 时, 有时能 够减少搜索过程, 方法是不去计算 S 属性的所有子集的闭包。这里是有助于减少工作量的一些规则:1. 不必考虑 S 所有属性全集的闭包。2. 不必考虑不包含任何函数依赖左边的属性集。3. 不必考虑包含不在任何函数依赖左边的一个属性的集合。键码。同样,B→C的左边也不是超键码。这两种情形下, 基于依赖B→C进行的分解都把 R 的属性分为模式{A, B}和{B, C}。设t是R的一个元组。我们可以写作t= (a,b,c) , 其中,a,b,c分别是t在属性A,B,C 上的分量。元组 t 在模式为{ A, B }的关系上的投影为( a, b) , 在模式为{B, C}的关系上的投影为(b,c) 。把 一个来 自{A, B}的元 组和一个 来自{B, C}的元组“连 接”起 来是可 能的, 只要它 们在B分量上一致。特别是, (a,b) 和(b,c) 连接就又回到了最初的元组t= (a,b,c) 。无论开始时的元组 t 是什么, 结论都是成立的; 我们总能把 t 的投影连接起来得到原来的 t。图 3. 37 连接投影关系中的两个元组但是, 得到开始时的 元组并不 足以保 证分解后的关系真实地再现原始的关系 R。比如 R 有两个元组,t= (a,b,c) 和v= {d,b,e}, 情况将会 如何呢?把 t 投影到{A, B}上, 我们得到 u = ( a, b) , 把 v 投影到{B,C}上, 得到w= (b,e) , 如图 3. 37 所示。由于 元 组 u 和 w 在 B 分 量 上一 致, 它 们 可以连接, 结果得到了元组x= (a,b,e) 。x可能是伪元组吗? 也就是说, ( a, b, e) 可能不是 R 的元组吧?由于我们假设了关系R存在函数依赖B→C,结论是“否”。回忆一下, 该依赖意 味着在 B 分量上一致 的R的任 何 两个 元 组, 必 然在C分 量 上 也一致。由于 t 和 v 在 B 分量上一致( 都是 b) , 它们在 C分量上也一致。这意味着c=e; 也就是说, 我们假设不相同的两个值实际上是相同的。因此, (a,b,e) 实际上就是(a,b,c) ; 即x=t。既然 t 在 R 中, x 必然也在 R 中。换句话说, 只要函数依赖 B → C 成立, 两个投影元组连接时就不可能产生伪元组。相反地, 连接生成的每个元组保证都是 R 的元组。上面的讨论可以推广到一般。上面假定A,B和C都是单个属性, 但对于它们是属性集的情况, 上面的讨论也同样适用。也就是说, 我们任取一 个违背BCNF的 依赖, 设B是左边的属性,C是右边的、而且未在左边 出现的属性, 而A是两边都没有出 现的属 性。我们可以得出下面的结论:· 如果我们按照 3. 7. 4 节的方法对关系进行分解, 则以所有可 能的方式对新关 系的·401·
7. 4. 2 建立权限 287……………………………………………………………………………7. 4. 3 权限检验处理 287………………………………………………………………………7. 4. 4 授予权限 289……………………………………………………………………………7. 4. 5 授权图 290………………………………………………………………………………7. 4. 6 取消权限 290……………………………………………………………………………7. 4. 7 本节练习 293……………………………………………………………………………7. 5 本章总结 294………………………………………………………………………………………7. 6 本章参考文献 296…………………………………………………………………………………第 8 章 面向对象查询语言 297…………………………………………………………………………8. 1 ODL 中相关查询的特性 297………………………………………………………………………8. 1. 1 ODL对象的操作 297……………………………………………………………………8. 1. 2 ODL中方法署名的说明 298……………………………………………………………8. 1. 3 类的范围 300……………………………………………………………………………8. 1. 4 本节练习 300……………………………………………………………………………8. 2 OQL介绍 301……………………………………………………………………………………8. 2. 1 面向对象的电影实例 302………………………………………………………………8. 2. 2 OQL类型系统 302………………………………………………………………………8. 2. 3 路径表达式 303…………………………………………………………………………8. 2. 4 OQL 中的 select-from-where 表达式 304………………………………………………8. 2. 5 消除重复 305……………………………………………………………………………8. 2. 6 复杂的输出类型 305……………………………………………………………………8. 2. 7 子查询 306………………………………………………………………………………8. 2. 8 对结果排序 307…………………………………………………………………………8. 2. 9 本节练习 307……………………………………………………………………………8. 3 OQL表达式的附加格式 308……………………………………………………………………8. 3. 1 量词表达式 308…………………………………………………………………………8. 3. 2 聚合表达式 308…………………………………………………………………………8. 3. 3 分组表达式 309…………………………………………………………………………8. 3. 4 HAVING 子句 311………………………………………………………………………8. 3. 5 集合运算符 311…………………………………………………………………………8. 3. 6 本节练习 312……………………………………………………………………………8. 4 OQL 中对象的赋值和建立 313…………………………………………………………………8. 4. 1 对宿主语言变量赋值 313………………………………………………………………8. 4. 2 从聚集中提取元素 313…………………………………………………………………8. 4. 3 获取聚集的每个成员 314………………………………………………………………8. 4. 4 建立新对象 314…………………………………………………………………………8. 4. 5 本节练习 316……………………………………………………………………………8. 5 SQL3 中的元组对象 316…………………………………………………………………………8. 5. 1 行类型 317………………………………………………………………………………8. 5. 2 说明具有行类型的关系 317……………………………………………………………8. 5. 3 访问行类型的分量 318…………………………………………………………………·Ⅺ·
元组进行连接就可以准确地恢复原始关系。如果我们不是基于一个函数依赖对关系进行分解, 则可能无法恢复原始关系。下面就是一个例子。例 3. 41 假设关系R的模式为(A,B,C) , 这和上面一样, 但依赖B→C不成立。而且,R可能包含两个元组:A B C1 2 _3 4 2 _5 R在模式为{A,B}和{B,C}的关系上的投影分别是:A B1 2 4 2 和B C2 3 2 5 由于四个元组的B分量值都相同, 都是 2, 一个关系的每个元组都可以跟另一个关系 的两个元组连接。于是, 当我们企图通过连接恢复R时, 我们得到的是:A B C1 2 _3 1 2 _5 4 2 _3 4 2 _5 也就是说, 我们得到的“太多了”; 其中有两个伪元组, ( 1, 2, 5) 和( 4, 2, 3) , 它们都不在原始关系R中。 □3. 7. 7 第三范 式偶尔我们会 遇到不属于BCNF的关系模式和 依赖, 但 却不想做进一步 的分解。下 面就是一个典型的例子。例 3. 42 假设关系 Booking 的属性如下:1.title, 电影名。2.theater, 正在上映该电影的电影院名。3.city, 电影院所在的城市。元组(m,t,c) 的意思就是一部名为m的电影正在c市的t影院上映。声明该关系中有如下函数依赖是合理的:theater→citytitle city → theater第一个依赖说明一个电影院只位于一个城市中。第二个意思不太明显, 它是基于实际·501·
的经验, 即不会在同一个城市的两个电影院预定同一部电影的票。我们声明这个依赖只是为了这个例子的需要。首先, 我们来确定键码。任何单个的属性都不是键码。例如, t itle 不是键码, 因为一部电影可以同 时在几 个电影院 放映, 而且可 以在几个 城市同时 上映。①同样,theater也不 是键码, 因为虽然 theater 函数决定了 city, 但还存在多屏幕 的电影院, 可以同时放映多 部电影。所以,theater并不函数决定title。最后,city也不是键码, 因为一个城市里通常有多个电影院, 而且会上映多部电影。另一方面, 在三个双属性集 中, 有两个 是键码。显然, {title,city}是键 码, 因为给 定的函数依赖说明这两个属性函数决定 theater。{theater,tit le}其实也 是键码。为 了说明原 因, 我 们从给 定的依赖theater→city开始。根据练习 3. 6. 3( a) 的增长规则, 我们知道 theater tit le→ city 也成立。这一点是很直观的, 如果theater单独函数决定city, 则theater加上tit le当然也函数决定city。剩下的一对属性, city 和 theater, 不能函数决定 title, 所以, 它们不能构成键码。我们断定只有两个键码:{tit le, city}{theater,tit le}现在我们立刻就会发现一个 BCNF 的违例。给定的函数依赖中有 theater → city, 但它的左 边(theater) 不是超 键码。因此, 我 们试图根据这个 违背BCNF的依 赖把原来的 关系模式分解为两个关系模式:{theater,city}{theater, title}但考虑函数依赖title city→theater时, 上面的分解是有问题的。可能存在与所分解的模式 对应的当前关系 ( 其模式满 足依赖 theater → city, 这一 点可在关 系{theater, city}中予 以检验) , 但连接起 来却得到 了违背title city→theater的关系。 例如, 假设 有两 个关系:theater cityGuild Menlo ParkPark Menlo Park和theater titleGuild The NetPark The Net根据每个关系 所满足的函数依 赖, 这两个 关系都 是允许的, 但连 接时, 我们得 到了违背 依赖 title city → theater 的两个元组:Theater city titleGuild Menlo Park The NetPar k Menlo Park The Net□·601·①在 这 个例 子 中, 假 设两 部“当前 的 ”电 影 不会 重 名, 虽 然 此前 我 们曾 经 考虑 到 不同 年 份制 作 的两 部 电 影可 能重名。
其他范式既然存在“第三范式”, 那第一、第二范式呢?实际上, 人们的确定义过第一、第二范式, 只不过现在很少使用。“第一范式”的条件很简单, 就是每个元组的每个分量都必须是原子值。“第二范式”比 3NF的限制稍微少一些, 允许关系中 存在传递依赖, 但不允许非平凡依赖的左边是键码的真子集。在 3. 8 节我们将看到, 还有“第四范式”。解决这 个问 题的方 法是 把 BCNF 限制 稍微放 宽一 些, 以便允 许某 些类 似例 3. 42 那样不能分解成BCNF关系的特 殊关系模式存在, 而不降 低检查一个关系中 的所有函数 依赖的能力。放宽后的条件称为“第三范式”(third normal form) , 即:· 如果对于任何非平凡依赖 A1 A2 …An → B, 或者{ A1 , A2 , …, An }是超键 码, 或者 B是某个键码的组成部分, 则关系 R 就属于“第三范式”( 3NF) 。注 意 3NF条件 和BCNF条 件 之 间 的区 别 在 于 句 子“或 者B是 某 个 键 码 的 组 成 部分”。这个句子“宽容了”诸如例 3. 42 中的依赖 t heater → city( 即允许该依赖合法存在) ,因为右边的city是一个键码的组成部分。证明 3NF的确可以胜任给它的任务不在本书的讨论范围之内。也就 是说, 我们 总可以无 损地把 一个 关系模 式分 解为 属于 3NF的模 式, 并 且所 有的 函数 依赖 都 可以 得到 检验。不过, 如果这些关系不属于BCNF, 模式中还将存在某种冗余。有趣的是, 我们观察到, 我们找到的这个例子的关系模式属于 3NF , 但不属于 BCNF,这个例子与以前见到的非BCNF的例子稍有不同。其中, 有关的函数依赖theater→city就是 一种 典型 的形式, 当 然, 这基于 这样 的事 实:“某个 电影 院是 定位一 个城 市的 唯一 事物”。然而, 另一个依赖title city→theater则来自于现实生活中电影制片公司制定的电影分配策略。通常, 函数依赖分为两类: 一些基于我们表示唯一事物( 比如电影和制片公司) 的事实, 另一些基于现实世界的实践经验,比如每个城市最多在一个电影院预定一场电影票。3. 7. 8 本节练 习练习 3. 7. 1: 对于下列的每个关系模式和函数依赖集:* (a)R(A,B,C,D) 和函数依赖AB→C,C→D和D→A。* (b)R(A,B,C,D) 和函数依赖B→C和B→D。( c) R( A, B, C, D) 和函数依赖 AB → C, BC → D, CD → A 和 AD → B。(d)R(A,B,C,D) 和函数依赖A→B,B→C,C→D,D→A。( e) R( A, B, C, D, E ) 和函数依赖 AB → C, DE → C 和 B → D。(f) 9R(A,B,C,D,E) 和函数依赖AB→C,C→D,D→B和D→E。做如下事情:( i) 找出所有违背 BCNF 的依赖。切勿忘记考虑不在给定的集合中但蕴含于其中的依赖。然而, 不必给出右边多于一个属性的违背BCNF的依赖。·701·
( ii) 必要时, 把给出的关系模式分解成属于 BCNF 的关系的聚集。(iii) 找出所有的违背 3NF的依赖。( iv) 必要时, 把给出的关系模式分解成属于 3NF 的关系的聚集。练习 3. 7. 2: 在 3. 7. 4 节我们曾经提到, 如果可能, 应该尽量扩展违 背BCNF的函数 依赖的右边。无论如何, 人们认为这是一个可选的步骤。考虑模式为属性集{A, B, C, D}的关系R, 函数 依赖为A→B和A→C。因为该关系唯一 的键码是{A,D}, 所以两 个依赖都 是BCNF 的违例。假设我们的出发点是根据函数依赖 A → B 来分解 R。最终得到的结果跟首先把BCNF的违例扩展成A→BC一样吗? 为什么?! 练习 3. 7. 3: 设 R 和练习 3. 7. 2 中的相同, 但函数依赖是 A → B 和 B → C。再次比较首先利用A→B分解和首先利用A→BC分解的结果。提示: 分解时, 需要考虑哪 些函数依赖在分解后得到的关系中成立。只使用给定的函数依赖( 仅包含分解后的一个模式中的属性) 是否足够? 给定依赖的导出依赖有没有作用?! 练习 3. 7. 4: 假设我们有一个关系模式 R( A, B, C) , 具有函数依赖 A → B。再假设我们决定把该模式分解为S(A,B) 和T(B,C) 。给出 一个关系R的实 例, 应像 3. 7. 6 节那样,让它在 S 和 T 上进行投影随后连接起来却得不到原来的关系实例。! 练习 3. 7. 5: 假设我们把关系R(A,B,C,D,E) 分解成S(A,B,C) 和另一些关系。 如果R 的函数依赖如下, 请给出 S 中成立的函数依赖:* (a)AB→DE,C→E,D→C和E→A。( b) A → D, BD → E , AC → E 和 DE → B。(c)AB→D,AC→E,BC→D,D→A和E→B。( d) A → B, B → C, C → D, D → E 和 E → A。每种情况只需要给出S的全依赖集的一个最小基即可。3. 8 多 值 依 赖“多值依赖”是一个断言, 指的是两个属性或属性集相互独立。我们以后将会看到, 这种情况是函数依赖概念的广义形式, 意味着每个函数依赖都包含一个相应的多值依赖。然而, 涉及属性集独立性的某些情况, 不能解释为函数依赖。在本节我们将寻找产生多值依赖的原因, 看看如何把多值依赖用于数据库模式设计。3. 8. 1 属性的 独立性及 其带来的冗 余偶尔会遇到这样的情况, 我们设计一个关系模式并发现它 属于BCNF, 但该关系 依然有和函数依 赖无关的某种冗 余。BCNF 模式 中存在冗余, 最常见的原因 是, 当我 们用 3. 2节描述 的方法 直接 把ODL模 式转换 成关 系模 式时, 某个 类的 两个或 多个 多值属 性的 独立性。例 3. 43 假设 类Star的定义中 包含姓名、地址集和该影星 主演的 电影集, 类似于 图2. 5, 但属性 address 的类型不同。这里提出的 Star 的定义如图 3. 38 所示。在图 3. 39 中, 我们给出了从图 3. 38 的定义直接得到的关系中某些可能的元组。表示·801·
地址集的方法与我们在图 3. 8 中表示的完全相同。该图中的元组已经扩展了属性 tit le 和year的相 应分量, 它 们是类Movie的键 码。这些属性通过 联系主演 (st arredIn) 表示与 该影星相关的电影。 in+terface Star{attribute str ing name;attr vibut e Set>Struct Addr {string street, string city}> address;relationship Set<Movie>starredIn inverse Movie: :stars;}图 3. 38 含有地址集和电影集的影星定义name street city title yearC.Fisher123 Maple St.Hollywood Star Wars1977 LC. Fisher 5 PLocust Ln. Mailbu Star Wars 1977 LC.Fisher123 Maple St.Hollywood Empire Strikes Back1980 LC.Fisher5 PLocust Ln.Mailbu Empire Strikes Back1980 LC. Fisher 123 Maple St. Hollywood Return of the Jedi 1983 LC.Fisher5 PLocust Ln.Mailbu Return of the Jedi1983 L图 3. 39 与电影无关的地址集让我们把注意 力集中 到图 3. 39Carrie Fisher的两个假 设地址 和三部最 著名的电 影上。把一个地址和一部电影相连, 而不和另一部相连, 是毫无道理的。因此, 表示地址和电影都是影星的独立特性的唯一方法就是让每个地址都和每部电影一起出现。但如果在所有的组合中重复地址和电影信息时, 显然存在冗余。例如, 图 3. 39 把Carrie Fisher的每个地址重复了三次( 每部电影对应一次) , 每部电影重复了两次( 每个地址对应一次) 。然而, 图 3. 39 给出的Star的关系模式中却没有BCNF的违例。事实 上, 根本就 没有非平凡的函数依赖。例如, 属性 city 之外的其他四个属性并不函数决定 city。可能有一个影星, 有两个住处, 在不同的城市里, 但街道地址却完全相同。这样, 就会有两个元组, 它们在 city 之外的所有属性上一致, 但在 city 上取值不同。因此,name street title year→city不是关系 Star 的函数依赖。我们请读者自己 去检验, Star 的五个属 性中没有一个可 以由其他四个属性函数决定。这就足以判断根本不存在非平凡函数依赖了( 读者也应该考虑,为什么这种推断是合理的?) 。既然没有任何非平凡函数依赖, 所以, 所有的五个属性构成了唯一的键码, 并且没有 BCNF 的违例。 □3. 8. 2 多值依 赖的定义“多值 依赖”是 关于某 个关 系R的陈述, 其 含义 是如 果 确定 了R的一 个 属性 集的 取值, 则其他某些特定属性的取值与该关系的所有其他属性的取值无关。更确切地说, 如果我们自己限定R的元组在属于A的每个属性上取某特定的值, 结果属于B的属性取值的·901·
集合与既不属于 A 也不属于 B 但属于 R 的属性取值的集合无关, 则我们称多值依赖A1A2 …An →→B1B2 …Bm在关系R中成立。再确切些, 如果对于关系R中在A的所有属性上取值一致的每对元组t 和 u, 我们可以在 R 中找到某个元组 v, 满足:1. 和t,u在A上取值一致,2. 和 t 在 B 上取值一致, 而且3. 和u在除了A和B之外R的所有属性上取值一致。则我们称这个多值依赖成立。注意, 上面的规则中t和u可以交换, 意味着存在第四个元组w, 它和u在B上一致,和 t 在其他属性上一致。结果是, 对于 A 的任何固定值, B 和其他属性的相关值在不同的元组中以所有可能的组合出现。图 3. 40 说明了多值依赖成立时v如何与t和u相关。图 3. 40 多值依赖保证v存在通常, 我们可 能假 设多值 依赖 的 A 和 B中的属性( 左边和右边) 是分开的。然而, 和函数依 赖 一 样, 如果 我 们 愿 意, 也 允 许 A 中的某些属性出现在右边。还要注意, 函数依赖中我们 从右边 只有 一个 属性 开 始, 并且 允许 右边为属性集的简化形式, 在 这一点上, 多值依赖与 函数依 赖不 同, 我们 必 须直 接考 虑右 边为属性集的 情 况。在 例 3. 45 中 我 们将 会 看到, 把多值 依赖 的右 边分 解 成单 个的 属性 并不总是可行的。例 3. 44 在例 3. 43 中我们遇到了一个多值依赖, 按我们的表示法可表示成:name→→st reet city也就是说, 对于每个影星的姓名, 其地址集伴随该影星主演的每部电影出现。作为运用该多值依赖正式定义的一个实例, 请考虑图 3. 39 的第一和第四个元组:Name street city title yearC.Fisher123 OMaple St.Hollywood Star Wars1977 C. Fisher 5 Locust Ln. Mailbu Empire Strikes Back 1980 如果假设 第一个元组为t, 第二个元组为u, 则多值依赖 断言我们 在R中 必然可以 找到一个 元组, 其 name 属性值为 C. F isher, street 和 city 取值 与第一个 元组 一致, 而其 他属性(title和year) 取值与第二个元组一致。的确有这样一个元组; 它就是图 3. 39 的第三个元组。类似 地, 也 可以 假设 上面 的第 二个 元 组为t, 而 第一 个元 组 为u, 则多 值 依赖 告诉 我们, R 中 存 在 一 个元 组, 它 在 属 性 name, street 和 city 上与 第二 个 元 组 一 致, 而 在 属 性name,tit le和year上与第一个元组一致。这个元组也是存在的; 它就是图 3. 39 的第二个元组。 □·011·
3. 8. 3 多值依 赖的推论关于多值依赖, 有许多规则, 和我们在 3. 6 节学过的有关函数依赖的规则类似。例如,多值依赖遵守· 平凡依赖规则, 即如果多值依赖A1A2 …An →→B1B2 …Bm 在某个关系中成立, 则A1A2 …An →→C1C2 …Ck也成立, 其中,C是B加上A中的一个或多个属性。反之, 我们也可以从B中删除一些属于 A 的属性, 并推导出多值依赖A1 A2 …An →→ D1 D2 …Dr其中 D 是在 B 中而不属于 A 的属性。· 传递规则, 即如果多值依赖 A1 A2 …An →→ B1 B2 …Bm 和 B1 B2 …Bm → → C1 C2 …Cm在某个关系中成立, 则A1A2…An→→ C1C2…Ck也成立。但多值依赖并不遵守分解/ 合并规则的分解部分。例 3. 45 再次考虑图 3. 39 中的关系, 其中我们看到多值依赖name →→ st reet city如果分解规则对于多值依赖成立, 则可以预料name→→st reet也成立。这个多值依赖的含义是每个影星的街道地址和其他属性( 包括城市) 无关。然而,这个陈述是错误的。例如, 考虑图 3. 39 的前两个元组。假定的多值依赖允许我们推导出这样的结果—— 街道属性值交换后的元组:name street city title yearC.Fisher5 Locust Ln.Hollywood Star Wars1977 sC. Fisher 123 Maple St. Mailbu Star Wars 1977 s依 然 在该 关 系中。 但这 些 不 是真 正 的 元 组, 例 如, 5 Locust Ln. 的 住 所 在 Malibu, 不 在Hollywood。 □然而, 我们还要学习几个涉及多值依赖的新规则。首先,· 每个函数 依赖都是多值依 赖。也就是说, 如果 A1A2 …An → B1 B2 …Bm , 则 A1 A2 …An →→ B1 B2 …Bm 。要了解 为什么, 先假设 某个关 系 R 中函 数依赖 A1 A2 …An → B1B2 …Bm 成立, 并假 设t 和 u是在 A 上一致的 R 的元组。为了证明多值依赖 A1 A2 …An →→ B1B2 …Bm 成立, 我们必须证明R还 包括一个元组v, 它和t,u在A上 一致, 和t在B上 一致, 而且和u在所 有其他属性上一致。但v可以是u。显然u和t,u在A上一致, 因为我们最初的假设就是这两个元组在 A 上一致。函数依赖 A1A2…An→ B1B2…Bm保证了 u 和 t 在 B 上一致。而且,u 和它 自 己 当 然 在 其 他 属 性 上 一 致。因 此, 只 要 函 数 依 赖 成 立, 相 应 的 多 值 依 赖 就成立。 ·111·
另一个在函数依赖世界里没有相应规则的是“互补规则”( complementation rule) :· 如果 A1 A2 …An →→ B1B2 …Bm 是关系 R 的多值依赖, 则 R 也满 足 A1 A2 …An →→C1C2 …Ck , 其中,C是不属于A,B的R的所有其他属性。例 3. 46 再次考虑图 3. 39 的关系, 其中我们声明了多值依赖name →→ st reet city互补规则说明name→→title year在该关系中也必然成立, 因为 t itle 和 year 是第一个依赖没有涉及的属性。从直觉上, 可以看出, 第二个依赖指的是每个影星都主演了一组电影, 而这些电影与影星地址无关。 □3. 8. 4 第四范 式如果把多值依 赖用于新的关系 分解算 法中, 那么, 在 3. 8. 1 节发现 的、由 多值依赖 引起的冗余是可以消除的。本节将介绍一种新范式, 称为“第四范式”( fourth normal form) 。在这种范式里, 随着违背BCNF的所有函数依 赖的消除, 所有的“非平凡”(nontrivial) ( 在下面所定义的意义下) 多值依赖也都消除了。结果是分解后的关系既不含我们在 3. 7. 1 节曾讨论过的、由函数依赖 引起的冗余, 也不含 我们在 3. 8. 1 节所 讨论的、由多 值依赖引 起的冗余。如果:1.B中的属性都不在A中;2. A 和 B 并未包含 R 的所有属性。则关系 R 的多值依赖 A1A2 …An →→ B1B2 …Bm 就是“非平凡的”( nontrivial) 。“第四范式”条件实质上是 BCNF 条件, 但它应用于 多值依赖而不是函 数依赖。正 式地陈述如下:· 如果 A1 A2 …An →→ B1 B2 …Bm 是非平凡的多值依赖, 且{ A1 , A2 , …An }是超键码,则关系 R 就属于第四范式( 4NF) 。也就是 说, 如 果一个关 系属 于 4NF, 则每 个非 平凡多 值依 赖实 际上就 是一 个左边 为超 键码的函数依赖。注意, 键码和超键码的概念只和函数依赖有关; 增加多值依赖并不改变“键码”的定义。例 3. 47 图 3. 39 的关系违背了 4NF 条件。例如,name→→st reet city是 非平 凡的 多值 依 赖, 而name本身 不 是超 键码。实 际上, 该关 系的 所 有属 性 是唯 一 的键码。 □第四范式实际上是 BCNF 的广义形式。回忆一下, 我们在 3. 8. 3 节讲过, 每个函数依赖也是一 个多值依赖。因此, 每个BCNF的违 例也是一 个 4NF的违 例。换句话 说, 属 于4NF的每个关系都必然属于BCNF。然而, 有些属于BCNF的关系却不属于 4NF。图 3. 39 就是一个很好的例子。该关系·211·
多值依赖的投影进行第四范式分解时, 我们需要找到在分解后的关系中成立的多值依赖。我们希望有更简单的方法找到这些依赖。然而, 像函数依赖中计算属性集的闭包之类的简单检验方法( 见 3. 6. 3 节) 是不存在的。实际上, 即使是关于函数和多值依赖集的推导规则的完备集, 就已经相当复杂了, 并已超出本书的讨论范围。参考文献部分提到了一些处理该主题的文献。幸运的是, 我们常常能利用传递规则、互补规则和交集规则( intersection rule) [ 练习 3. 8. 7(b) ] 得到与分解的结果相应的多值依赖。我们建议读者通过实例和练习对此作进一步尝试。的唯一键码是所有五个属性, 而且没有非平凡函数依赖。因此, 它确实属于 BCNF。然而,正如我们在例 3. 47 中所看到的, 它不属于 4NF。3. 8. 5 分解成 第四范式4NF的分解 算法 与BCNF的分 解算法 非常 类似。首 先, 我们要 找到 一个 4NF的 违例, 比如A1A2 …An →→B1B2 …Bm , 而{A1 ,A2 , …An }不是超键码。注意: 这个多值依赖可能的确是一个多值依赖, 也可能是 从相应的函数依 赖A1A2 …An →B1B2 …Bm 导出的 多值依赖, 因为每个函数依赖都是一个多值依赖。然后, 我们把含有 4NF 违例的关系 R 的模式分解为两个模式:1.A和B中的属性;2. A 中的属性以及既不属于 A 也不属于 B 的 R 的所有其他属性。例 3. 48 让我们继续研究例 3. 47。我们观察到name →→ st reet city是一个 4NF的违例。上面的分解规则告诉我们, 可用两个模式来代 替原来的包含五 个属性的关系模式—— 一个模式只包含上面的多值依赖所涉 及的三个属性, 另一个模式 由该依赖的左边, 即 name, 加上未 在该依赖中出现的 属性组成。这些属 性是 title 和 year 。因此, 下面的两个模式{ name, street, city }{name,title,year}就是 分解的结果。每个模 式中都没有非平 凡多值( 或 函数) 依赖, 所以它 们属于 4NF。注意, 在模式为{ name, street, city }的关系中, 多值依赖name→→st reet city是平凡的, 因为它包含了所有的属性。类似地, 在模式为{ name, title, year }的关系中, 多值依赖name→→title year也是平凡的。如果有一个或两个分解后的模式不属于 4NF , 我们就必须把非 4NF 的模式继续分解。 □·311·
对于 BCNF 分解, 每一 步分解所得的模 式的属性数目都 绝对比分解前 更少, 因此, 最终我 们将得到 不需要 进一步分 解的模式; 也 就是说, 它们属 于 4NF。而且, 我们 在 3. 7. 6节给出的证明分解正确的论证也适用于多值依赖。当我们由于多值依赖A1A2 …An →→B1B2 …Bm而分解关系模式时, 该依赖足以用来证明这一断言是正确的, 即我们可以从分解后的关系重构原始的关系。3. 8. 6 范式间 的联系正如我们曾经提到的, 4NF隐 含BCNF, 而BCNF又隐含 3NF。因此, 对于任何 关系模式而言, 满足三个范式的关系实例集之间的联系如图 3. 41 所示。也就是说, 如果一组元组满足 4NF条件, 那么它 必 然 满 足 其 他 两 个 范 式 条 件, 而且如果它满足BCNF条件, 那图 3. 41 4NF 隐含 BCNF 隐含 3NF么它必然属于 3NF 。然而, 随着该模式所假设的函数依赖不同, 有一些元组的集 合可能属于 3NF, 而不属于 BCNF 。类似地, 对于假设的函数和多值依赖的特定集合, 有 一些元组的集合属于 BCNF, 而不属于 4NF 。另一种对范式进行比较的方法是, 比较它们 为分解 成相 应的范 式后 得 到的 一组 关系所做的保证。这方面的观察结果汇总在图3. 42 的表中。也就是说, BCNF ( 从而 4NF)消除了由函数依赖带来的冗余和其他异常,而只 有 4NF 消除 了 由于 非 平 凡多 值 依赖、而不是函数依赖的存在而带来的附加冗余。通常, 3NF 就足以 消除这 种冗 余了, 但有 些例子中, 它也无能为力。我们总是可以选择分解成 3NF, 从而保持函数依赖; 也就是说, 它们在分解后的关系中依然成立( 虽然本书中并未讨论这么做的 算法) 。BCNF 不保证 函数依赖的保持性, 并且, 没有一个范式保证多值依赖的保持性, 虽然在典型的情形下, 依赖是保持的。特性 3 NF BCNF4 NF消除函数依赖引起的冗余 大部分 是 是消除多值依赖引起的冗余 否 否 是保持函数依赖 是 可能 可能保持多值依赖 可能 可能 可能图 3. 42 范式及其分解的特性3. 8. 7 本节练 习* 练习 3. 8. 1: 假设我们有一个关系R(A,B,C) , 存在多值依赖A→→B。如果已知元·411·
8. 5. 4 引用 318…………………………………………………………………………………8. 5. 5 利用引用 320……………………………………………………………………………8. 5. 6 引用的作用域 320………………………………………………………………………8. 5. 7 作为值的对象标识 321…………………………………………………………………8. 5. 8 本节练习 323……………………………………………………………………………8. 6 SQL3 的抽象数据类型 324………………………………………………………………………8. 6. 1 ADT的定义 324…………………………………………………………………………8. 6. 2 ADT 方法的定义 327……………………………………………………………………8. 6. 3 外部函数 329……………………………………………………………………………8. 6. 4 本节练习 329……………………………………………………………………………8. 7 ODL/ OQL 和 SQL3 方法的比较 330……………………………………………………………8. 8 本章总结 331………………………………………………………………………………………8. 9 本章参考文献 332…………………………………………………………………………………·Ⅻ·
组 ( a, b1 , c1 ) , ( a, b2 , c2 ) 和 ( a, b3 , c3 ) 在 R 的当前实例中, 我们可以判断还有哪些元组必然也在R中?* 练习 3. 8. 2: 假 设我们 有一个关 系, 用以 记录每 个人 的名 字、社 会保 险号 和出生 日期。还包括他/ 她的每个孩子的名字、社会保险号和出生日期, 以及他/ 她所拥有的每辆车的序号和型号。更确切地说, 该关系的所有元组形式如下:(n,s,b,cn,cs,cb,as,am)其中,1.n是社会保险号为s的人的名字。2. b 是 n 的出生日期。3.cn是n的一个孩子的名字。4. cs 是 cn 的社会保险号。5.cb是cn的出生日期。6. as 是 n 的一辆车的序号。7.am是序号为as的汽车的型号。对于这个关系(a) 写出我们期望保持的函数依赖和多值依赖。( b) 给出把该关系分解成 4NF 的结果。练习 3. 8. 3: 对于下面的每个关系模式和依赖:( a) R( A, B, C, D) , 存在多值依赖 A →→ B 和 A →→ C。(b)R(A,B,C,D) , 存在多值依赖A→→B和B→→CD。( c) R( A, B, C, D) , 存在多值依赖 AB →→ C 和函数依赖 B → D。(d) R(A,B,C,D,E) , 存在多值依赖A→→B,AB→→C和函数依赖A→D和AB → E。做如下练习:( i) 找出所有 4NF 的违例。(ii) 把关系分解为属于 4NF的关系模式的聚集。! 练习 3. 8. 4: 在练习 2. 3. 2 中, 我们讨论了关于联系 Births 的四个不同假设。对于每个假设, 指出希望在结果关系中保持的多值依赖( 而不是函数依赖) 。练习 3. 8. 5: 为 什么不希望例 3. 43 中的五 个属性中的任一 个由其他四个函 数决定, 请 给出非正式论证。! 练习 3. 8. 6: 用多值依赖的定义, 证明互补规则为什么成立。! 练习 3. 8. 7: 证明下列多值依赖规则:* ( a) F并集规则( union rule) 。如果 X, Y 和 Z 是属性集, X →→ Y 并且 X →→ Z, 则X→→ (Y∪Z) 。( b) L交集规则( intersection rule) 。如果 X, Y 和 Z 是属性集, X →→ Y 并且 X →→Z, 则X→→ (Y∩Z) 。( c) A差集规则( difference rule) 。如果 X, Y 和 Z 是属性 集, X →→ Y 并且 X →→Z, 则X→→ (Y-Z) 。·511·
( d) 平凡多值依赖。如果 YX, 则 X →→ Y 在任何关系中都成立。(e) C平凡多值依赖的另一个来源。 如果X∪Y是关系R的全 部属性, 则X→ →Y在 R 中成立。(f) 消去左、右边共同的属性。如果X→→Y成立, 则X→→ (Y-X) 也成立。! 练习 3. 8. 8: 给出反例关系, 说明下列多值( 或函数) 依赖规则为什么不成立。* (a) 如果A→→BC, 则A→→B。( b) 如果 A →→ B, 则 A →B。(c) 如果AB→→C, 则A→→C。! 练习 3. 8. 9: 从 ODL 到关系的转 换常常会引入多 值依赖。给出采用 3. 2. 2 节和 3. 2. 5节的关系模式策略时, 从多值的属性和联系中发现多值依赖的一些原则。3. 9 数据库模式实例我们已经看 到了直接 从ODL或E/R设 计构造关 系时可能 产生的 各种问题, 也已 经看到了如何处理有时出现的异常, 现在让我们专注于一个关系的数据库模式上, 在本书下一部分( 致力于用户的数据库编程方面) 的实例中将会用到这一数据库模式。我们的数据库模式建立在 电影、影星 和制片公司不断 滚动的 实例的基 础上, 采用规 范化的关 系, 类 似于前面章节建立的那些关系。然而, 它还包含了前面的实例中没有出现的某些属性, 而且还包含一个以前没有出 现的 关系 —— MovieExec。这 些改动 的目 的是 为了在 第 4 到第 8章的实例中, 给我们提供 一些学习表示信息 的不同数据类型 和不同方法的机 会。图 3. 43给出了这个数据库模式。我们的关系模式包括五个关系。每个关系的属性及其指定的域都列出来了。其中, 每个关系的键码 属性在图 3. 43 中都用大写 表示, 不过在 叙述过程 中提到 它们时, 它们还 像以前一样, 都是小写的。例如, 关系StarsIn的所有三个属性一起构成键码。关系Movie具有六个属性: title 和 year 一起构成了它的键码, 这都和以前一样。属性 tit le 是字符串, 而year是整数。与我们以前见过的关系模式相比, 该模式的主要改动如下:· 对 于电影 行政长官 ( 制 片公司总 裁和电 影制片 人) 而言, 有certificate number( 证书号) 的概念。这个证书号是我们假想的唯一的整数, 它由某个外部的权威机构维护, 该机构或许是行政长官注册处或“协会”。· 我们将证书号作为电影行政长官的键码。即使是 电影明星也不一 定都有证书号,所以我们仍 然用name作为影 星的键码。这种 决定可能 有些不 切实际, 因为两 个影星可能重名。但为了说明某些不同的选项, 我们还是采用这种方法。· 我 们 为 电 影 引 入 了 另 一 个 特 性 —— 制 片 人, 他 的 信 息 由 关 系Movie的 新 属 性producerC# 表示。该属性应是制片人的证书号。制片人应该是和制片公司总裁一样的电影的行政长官。在MovieExec关系中也可能有其他的行政长官。· Movie 的 属 性 filmT ype 由 枚 举类 型 改成 布 尔值 的 属 性, 称为“inColor”: 彩 色 电影, 其值为“真”(true) ; 黑 白电影, 其值为“假”(false) 。这么做的原因 是有些数 据·611·
库语言不支持枚举类型。· 为 电 影 明星 增 加 了 属 性gender( 性别 ) 。 它的 类 型 是“字 符 ”, 或 者 是‘M’, 表 示“男”, 或 者 是‘F ’, 表 示“女 ”。 还 增加 了 属 性 birthdate( 出生 日 期 ) , 它 的 类 型 是date( 日期) —— 许多商用数据库系 统支持的一种特 殊类型, 或者, 如果我 们愿意,也可以只是一个字符串。· 所有的地址都使用字符串, 而不是街道 和城市的组合。这样 做使得不同关系 中的地址容易比较, 而且简化了地址操作。最后作为总 结, 我 们对五个 关系及 其属性以 及这些 关系从以 前的 ODL 或 E/ R 设 计的演变过程给出简单的注释。 Movie(TITLE: string,YEAR:integer,length: integer,inColor:boolean,studioName: string,producerC# :integer)StarmsIn(MOVIETIT LE: string,MOVIEYEAR:integer,STARNAME: string )MoMvieStar(NAME: string,address:str ing,gender: char,birthdate:date)Mo MvieExec (name:string,address: str ing,CERT# :integer,netWorth: integer )Stu Odio (NAME:string,address: string,presC# :integer)图 3. 43 关于电影的数据库模式实例1. Movie 是 例 3. 36 的关 系 Movie 分 解 得到 的 关 系 之 一, 我 们 在 其 中 增 加 了 属 性producerC# , 表示该电影的制片人。2.StarsIn是例 3. 36 的关系分解得到 的另一个关系。如 果我们从同名的ODL类定义出发构造关系 Star, 然后将其分 解为 BCNF , 也需要 同样的关系 StarsIn。也就是说, 从图 2. 5 的ODL定 义 出 发, 我 们 将 会 得 到 具 有 属 性name,address,title和year的 关 系Star。后两个属性代表联系st arredIn。我们发现{name,title,year}是键码, 但存在函数依·711·
赖 name → address。 因 此, 该 关 系 将 分 解 为 模 式 { name, address } ( 已 扩 展 为 关 系MovieSt ar) 和{name,title,year}( 实质上就是关系St arsIn) 。我们的关系StarsIn也表示了图 2. 8的 E / R 图中的联系 Stars-in。3. 10 本 章 总 结 关系模型: 关系是表 示信息的表。列以属 性开头; 每个属性都有相关 的域或数 据类型。行称为元组, 对于关系的每个属性, 元组都有一个分量与之对应。 模式: 关系名和关系的属性一起构成了关系模式。关系模式的聚集构成了关系数据库模式。一个关系或关系聚集对应的特定数据称为该关系模式或该数据库模式的实例。 实体 集转换为 关系: 实 体集对 应的 关系中, 对 于实 体集的 每个 属性, 关系 中都 有一个属性与之对应。弱实体集是例外, 弱实体集 E 对应的关系中还必须包含有助于标识E的实体的其他实体集的键码属性。 联系 转换为关 系: E/ R 联系对应 的关系中 包含参 与该 联系的 每个 实体集 的键 码属性所对应的属性。 ODL 类转换为关系: ODL 类 C 对应的关系中, 对于类的每个属性, 关系中都 有一个属性与之对应。该关系中 还可能包含通过 某个联系与C相连的类D的键 码属性。由于 ODL 的联系存在反向联系, 我们建议把 C 到 D 的多对一联系放在 C 中,而不放在D中。 ODL 多对多联系转换为关系: 多对多的联系可以放在两个类的任一个中, 但它们会造成 关系的 元组数目 激增。关系 设计中的 这个缺 陷可以通 过规 范化过 程来 消除。另一种方法是, ODL 中的多对多联系可以像 E/ R 模型那样, 用单独的关系来表示。 子类结构转 换为关系: 一种方法是把实 体或对象分散在 不同的子类中, 并为每 个子类建立一个关系, 其中包含所有必要 的属性。第二种方法 是用一个主关系 来表示所有的实体或对象, 这种主关系只包 含最通用的类的属 性。而子类的实体 或对象则用对应于它们所属的子类的特定关系来表示。这些关系只包含通用类的键码属性和该子类的特有属性。 函数 依赖: 函数依赖 是对 于关系 中在 某个特 定的 属性 集上一 致的 两个元 组必 然在某个其他特定属性上也一致的陈述。 键码: 一个关系的超键码是函数决定该关系所有属性的属性集。键码是其任何真子集都不能函数决定所有属性的超键码。 函数 依赖的推 论: 存在许多 规则, 我们可以 藉此推 出函数依 赖X→A在 满足 其他某个给定函数依赖集的任何关系实例中成立。证明 X → A 成立, 通常最简单的方法是计算X的闭包, 即利用给定的函数依赖扩展X, 直到包含A。 分解关系: 我们可以把一个关系模式分解为两个。只要分解得到的两个关系模式中至少有一个, 其属性构成一个超键码, 就可能不损失信息。·811·
BC 范式: 如果一个关系 中仅有的非平凡 函数依赖都表明是 某个超键码函数 决定其他某个属性, 则该关系属于BCNF。把任何关系分解为BCNF关系的聚集 而不损失信息是可能的。BCNF 的主要优点是消除了由函数依赖带来的冗余。 第三范式: 有时, 分解成 BCNF 会妨碍我们检 验某些特定的函 数依赖。BCNF 的一 种宽松形 式, 称 为“3NF”, 即 使X不是超 键码, 但 只要A是 某个 键码的 组成 部分, 就允许存在函数依赖X→A。3NF并不能保证消除由函数依赖带来的所有冗余, 但通常情况下能做到全部消除。 多值 依赖: 多值依赖 是对 于一个 关系 的两个 属性 集取 值的集 合出 现各种 可能 组合的陈述。产生多值依赖的常见原因是所设计的关系代表了含有两个或多个多值的属性或联系的 ODL 类。 第四范式: 关系中的 多值依赖也可能 带来冗余。4NF类似 于BCNF, 但还禁止 非平凡多值依赖( 除非它们实际上就是BCNF允许的函数依赖) 。把一个关系分解为4NF 而不损失任何信息是可能的。3. 11 本章参考文献Codd的关于关系模型的经典论文是[ 4] 。该论文除了基本关系概念之外, 还介绍了函数依赖的思想。其中 也描述了第三范 式, 而Boyce-Codd范式是Codd在稍后的一篇 论文[ 5] 中描述的。多值依赖和第四范式是Fagin在[ 7] 中定义的。不过, 多值依赖 的思想也独立地 出现在[ 6] 和[ 9] 中。Armstrong( 阿姆斯特朗) 首先研究了函数依赖推导规则( 见[ 1] ) 。本章所涉及的函数依赖规则( 包 括我们称之为“Armstrong( 阿姆 斯特朗) 公理”的规则) 以及多值依赖推 导规则, 都来自[ 2] 。通过计算属性集的闭包来检验函数依赖的技术来自[ 3] 。还有许多算法和/ 或该算法的可行性证明在本书中并未给出。这些内容包括对以下各种问题的解释: 推导函数 依赖的闭包算法 何以成 立, 如 何推导多 值依赖, 如何 把多值依 赖投影到 分解后的 关系上, 以 及在不 损失 检验函 数依 赖的 能力的 情况 下如何 分解 成 3NF。这些和其他有关依赖的内容在[ 8] 中均有解释。[ 1] 'Armstrong, W. W. , Dependency structures of database r elationships, Proceedings of the 1974IFIP Congress,pp. 580~583.[ 2] 'Beeri,C. ,R.Fagin,and J.H.Howard,A complete axiomatization for functional andmultivalued dependencies, ACM SIGMOD International Conference on Management of Data, pp.47~61, 1977.[ 3] 'Bernstein,P.A. ,Synthesizing third normal form relations from functional dependencies,ACMTransactions on Database Systems, 1: 4,pp. 177~298, 1976.[ 4] 'Codd, E. F. , A relational model for large shared data banks, Comm. ACM 13: 6, pp. 377~387,1970.[ 5] 'Codd,E.F. ,Fur ther normalization of the database relational model,in Database Systems(R.·911·
Rustin, ed. ) , Prentice-Hall, Englewood Cliffs, NJ, 1972.[ 6] 'Delobel,C. ,Normalization and hierarchical dependencies in relational data model,ACMTransactions on Database Systems, 3: 3,pp. 201~222, 1978.[ 7] 'Fagin, R. , Multivalued dependencies and a new normal form for relational databases, ACMTransactions on Database Systems, 2: 3,pp. 262~278, 1977.[ 8] 'Ullman, J. D. , Pr inciples of Database and Knowledge-Base Systems, Volume I, ComputerScience Press,New York, 1988.[ 9] 'Zaniolo, C. , and M . A. Melkanoff, On the design of relational database schemata, ACMTransactions on Database Systems, 6: 1,pp. 1~47, 1981.·021·
第 4 章 关 系 模 型 中 的 运 算 在这一章, 我们开始从用户的观点学习数据库。通常, 用户的主要问题是查询数据库,也就是编写程序来回答关于数据库当前实例的询问。在这一章, 我们将从抽象的观点出发学习数据库查询的问题, 定义主要的查询运算符。虽然ODL使用原 则上可 以对数据 进行任 何运算的 方法, 而E/R模型不 包含数据 操作的特定方式, 但关系模型有对数据进行“标准”运算的具体集合。因此, 我们对数据库运算的学习理论上将集中于关系模型及其运算。 这些运算可以用代数( 称为关系代数) 表示,也可以用逻辑的形式( 称为“Datalog”) 表示。我们将在本章学习这两种表示法中的每一种。以后章节使我们看到今天的商业数据库系统向用户提供的语言和特性。抽象查询运算符将主要出现在第 5 至 7 章讨论的 SQL 查询语言的运算中。然而, 它们也在第 8 章提到的OQL语言中出现。4. 1 关 系 代 数为 了 开 始 关 于 关 系 运 算 的 学 习, 我 们 应 该 学 习 一 种 特 殊 的 代 数, 称 为 关 系 代 数( relational algebra) , 它包含某些简单而有效的方法来从旧关系构造新关系。关系代 数中的表达 式 (expression) 首 先 从 作 为 运 算 数 的 关 系 开 始; 关 系 可 以 用 关 系 名 ( 例 如,R或Movies) 表示, 也 可以显式地表示 为其元组的列表。然 后, 我 们可以 通过把下 面将要描 述的任何运算符用于关系或用于关系算术的较简单的表达式, 逐渐构造更为复杂的表达式。一个查询( query) 就是关系算术的一个表达式。因此, 关系代数就是 查询语言的第一 个具体实例。关系代数运算分为四大类:1. 普通的集合运算—— 并、交和差 —— 用于关系。2. 删除部分关系的运算:“选择”将删除某些行( 元组) , 而“投影”则删除某些列。3. 合并两个关系元组的运算, 包括笛卡 尔积—— 把两个关系的 元组以所有可能 的方式组成对, 以及各种“连接”运算 —— 从两个关系的元组中有选择地组成对。4. 称 为“改 名”的运算 并不影响 关系的元 组, 但是改变 关系模式, 也 就是改变 属性 的名字和/ 或关系本身的名字。这些运算并不足以对关系做任何可能的计算; 实际上, 它们是很有限的。但是, 它们包括了我们真正想对数据库进行的大部分处理, 它们构成了标准关系查询语言 SQL 的 主要部分, 正如我们将在第 5 章看到的那样。然而, 我们将在 4. 6 和 4. 7 节简要讨论存在于实际查询语言( 如 SQL) 之中但尚不属于关系代数部分的某些计算能力。·121·
4. 1. 1 关系的 集合运算集合的三个最普通的运算是并、交和差。我们假定读者都熟悉这些运算, 对于任意集合 R 和 S , 这些运算定义如下:· R∪S,R和S的并, 是在R或S或两者中元素的集合。一个元素在并集中只出现一次, 即使它在R和S中都存在。· R∩S, R 和 S 的交, 是 R 和 S 中都存在的元素的集合。· R—S,R和S的差, 是在R中而不在S中的元素的集合。注意:R—S不 同于S—R, 后者是在S中而不在R中的元素的集合。当我们对关系进行这些运算时, 需要把某些条件加在 R 和 S 上,1. R 和 S 的模式必须具有相同属性集。2. 在计算元组集合的集合论并集、交集或差集之前,R和S的列需要 排序, 以使 两个关系的属性顺序相同。有时我们希望对属性数相同但属性名不同的关系进行并、交或差运算。如果这样, 就可以利用将在 4. 18 节讨 论的改名运算符 来改变一 个或两个 关系的 模式给它 们以相同 的属性集。例 4. 1 假定我们有两个关系 R 和 S, 这是 3. 9 节关系 MovieStar 的实例。R 和 S 的当前实例在图 4. 1 中给出。名字 地址 性别 出生日期Carrie Fisher 123 NMaple St. , Hollywood F 9 v/ 9/ 99Mark Hamill456 NOak Rd. ,Brentwood M8 v/ 8/ 88关系R名字 地址 性别 出生日期Carrie Fisher 123 Maple St. , Hollywood F 9 / 9/ 99Harr ison Ford789 Palm Dr. ,Beverly Hills M7 / 7/ 77关系S图 4. 1 两个关系并集R∪S如下所示:名字 地址 性别 出生日期Carr ie Fisher 123 0Maple St. , Hollywood F 9 / 9/ 99Mark Hamill456 0Oak Rd. ,Brentwood M8 / 8/ 88Harrison Ford789 0Palm Dr. ,Beverly Hills M7 / 7/ 77 注意, 两个关系中有关Carrie Fisher的两个元组在结果中只出现一次。交集R∩S如下所示:·221·
名字 地址 性别 出生日期Carr ie Fisher 123 0Maple St. , Hollywood F 9 / 9/ 99 现在, 只有一个关 于 Carrie Fisher 元组, 因为只 有它出现在两个 关系中。差集 R—S如下所示:名字 地址 性别 出生日期Mark Hamill 456 Oak Rd. , Brentwood M 8 G/ 8/ 88 也就是说, F isher 和 Hamill 元组出现在 R 中, 因此是 R—S 的候选元组。然而, Fisher元组也出现在S中, 因此不在R—S中。 □4. 1. 2 投影投影运算符用来从关系 R 产生一个只有 R 的某些列的新关系。表达式 πA1, A2, … , An( R)的值是一个关系, 该关系只有R的属性A1 ,A2 , …,An 所对应的列。 结果值的模式是 属性集{A1 , A2 , …, An }, 按照惯例以列出的顺序表示它。例 4. 2 考虑具有在 3. 9 节描述的关系模式的关系Movie。该关系的实例在图 4. 2 中给出。我们可以用表达式πtit l e, y ear, lengt h (Movie)将该关系投影到前三个属性上。title year length inColor studioName producerC#Star Wars1977 124 vtrue Fox12345 Mighty Ducks1991 104 vtrue Disney67890 Wayne' s World 1992 95 _true Paramount 99999 图 4. 2 关系 Movie结果关系如下所示:title year lengthStar Wars 1977 124 ]Mighty Ducks1991 104 ]Wayne' s World 1992 95 F 作为另一个例子, 我们可以用表达式πinCol or (Movie) 投影到属性inColor上。结果是单列关系inColortrue注意, 在结果关系中只有 一个元组, 因为图 4. 2 的 所有三个元组 在属性inColor的 分量值相同。 □·321·
4. 1. 3 选择选择运算符 应用到关系 R, 将产生 作为 R 元组的 子集的新关系。结果关 系中的元 组是满足某种条件 C 的元组, 该条件与关系 R 属性有关。我们把这种运算记为 σC( R) 。结果关系的模式和R的模式相同, 并按照惯例用和R所用的同样的顺序表示属性。C 是我们从 传统的编程语言 熟悉的条件表达 式, 例如, 在编程语 言 C 或 Pascal 中, 条件表达式在关键字if的后面。唯一的不同在于条件C中运算项是常量或R的属性。我们通过用R的元 组t中每个属 性A对应的 分量, 代替出 现在条件C中的 每个属 性A, 把 条件 C 用于 R 的每个元组 t。如果代替条件 C 的每个属性以后条件 C 为真, 那么, 元组 t 就是出现在σC (R) 的结果中的元组之一; 否则t不出现在结果中。例 4. 3 让关系Movie如图 4. 2 那样。那么表达式σleng t h≥ 100 (Movie) 的值是title year length inColor studioName producer C#Star Wars1977 v124 true Fox12345 Mighty Ducks 1991 v104 true Disney 67890 第 一个 元组满 足条 件lengt h≥100, 因为当 我们 用第 一个元 组中 属性length的分 量值 124 来代替 length, 条件就变成 124≥100。后面 的条件为真, 所以我们取第 一个元组。可以用同样的理由说明图 4. 2 的第二个元组为什么也出现在结果中。第三个元组的lengt h分量为 95。于是, 当我们代替length时, 条件变成 95≥ 100, 为假。因此, 图 4. 2 的最后一个元组未出现在结果中。 □例 4. 4 假设我们想要在关系Movie( title, year, length, inColor , studioName, producerC# )中表示Fox公司 最少 100 分钟 长的电 影, 我们 可以用 一个 较复杂 的条 件得 到它, 该条 件包含两个子条件的与(AND) 。表达式如下:σlen gth≥ 10 0 A N D s t udi oName= 'F ox ' ( Movie)如下元组Title Year length inColor studioName ProducerC#Star Wars 1977 v124 true Fox 12345 E是结果关系中仅有的一个。 □4. 1. 4 笛卡尔 积两个集合 R 和 S 的笛卡尔 积( 或 只是乘积) 是元素对的集 合, 该元素 对是通过选择 R的任何元素作为第一个元素,S的元素作为第二个元素构成的。该乘积用R×S表示。当R 和 S 是关系时, 乘积本质上相同。然而, 因为 R 和 S 的成员是元组, 通常包含多个分量,由R的元组和S的元组构成的元组对是 一个更长的元组, 其中每个 分量都对应于组 成元组的一个分量。按现在的顺序,R的分量在S的分量之前。·421·
前 言本 书是从斯坦福大 学的“数据 库入门”( CS145) 的课程 笔记演变而来 的。CS145 是 五门系列课程 的第一门。①由于Arthur Keller颇有创意的讲 授, 使这 门课逐步 发展成着 重于数据库的设 计和编程的课程, 而这两方 面内容对 计算机科 学专业 的大多数 学生来说 是最有用的。这门课还包括一个内容广泛、不断滚动的课外工程项目, 供学生设计并实现一个具体的数据库应用。与该工程项目相关的作业、其他课外作业、测验以及其他课程资料都可以从本书的主页上得到; 请参阅“万维网(World Wide Web) 上的支持”部分。本 书 的 使 用本书适用于讲授一学期的课程。如果像CS145 这样按四分之一学年( 译注: 每学年分为四学期, 这种制度的一学期) 的课程来安排, 就不得不省略或跳过书中的某些内容。最好由教 师 自 己决 定 削 减 哪 些内 容, 但以 下 内 容 显 然是 可 以 削 减 的: 有 关Datalog的部 分,SQL 编程的高级部分以及 SQL3 的细节部分。如果课程中安排了不断滚动的工程项目, 那么, 提早讲授SQL语句部分是很重要的。可以推后讲授 的内容包括: 有关 Datalog 的部分, 第 5 章和第 6 章的 SQL3 部分 以及第 3章的某些理论部分( 但是, 如果学生们在开始进行SQL编程之前, 想设计出优秀的关 系模式的话, 他们就需要规范化的知识, 或许还需要多值依赖的知识) 。预 备 知 识我们把本书定位于“夹层”水平, 即高年级本科生和低年级研究生水平。这门课程正规的预备知识相当于大学二年级 的水 平: (1) 数据 结构、算法 和离 散数学; ( 2) 软件系 统、软件工程和编程语言。学生们对以下内容至少要有初步的了解: 代数的表达式和定律、逻辑、基本数据结构( 如搜索树) 、面向对象的编程概念以及编程环境。我们相信, 按照典型的计算机科学专业的教学计划, 到大学三年级结束时, 学生们肯定会拥有充分的背景知识。练 习本书包括多方面的练习, 几乎每节都有。我们把比较难的练习或练习中比较难的部分··①后 面的四 门是 : 数据 库系 统原理 , 数 据库 系统实 现的工 程训 练, 事务 和分布 式数据 库, 以及 数据库 理论 。
结果关系的关系模式是 R 和 S 的模式的并集。然而, 如果 R 和 S 偶然有某些公共属性, 那么, 我们需要为每个相同属性对的至少一个属性引入新名。为了区别既在R的模式中又在 S 的模式中的属性 A, 我们对来 自 R 的属性用 R. A 表 示, 对来自 S 的属性用S. A表示。例 4. 5 为了简明扼要, 让我们使用一个解释乘积运算的抽象例子。假设关系 R 和 S具有图 4. 3 中给出的模式和元组。那么, 乘积R×S包括该图中给出的六个元组。注意, 我们如何将两个R元组中的每一个和三个S元组中的每一个组成对。因为B是两个模式中的属性, 我们已经在 R× S 的 模式中使用了 R. B 和 S . B。其他属 性不会混淆, 于是, 它们的名字未加改变地出现在结果模式中。 □A B1 2 3 4 关系RB C D2 5 6 4 7 8 9 10 11 关系SA R. B S. B C D1 2 12 _5 6 1 2 14 _7 8 1 2 19 _10 11 3 4 12 _5 6 3 4 14 _7 8 3 4 19 _10 11 结果R×S图 4. 3 两个关系及其笛卡尔积4. 1. 5 自然连 接我们发现, 只把两个关系 中在某个方面 匹配的 元组成对 地连接 起来比得 到两个关 系的乘积有更多的需求。匹配的最简单的类型是两个关系 R 和 S 的自然连接, 表示为 R S, 只有在R和S的模式的任何公共属性上一致的R和S的元组才会成对地出现在自然图 4. 4 连接元组连接的结果中。更确切地说, 假设 A1, A2, …, An是在 R 和S的模式中都有的属性, 当且仅当R的元组r和S的元组s在 A1 , A2 , …, An 每 个属性上 都一致 时, R 的元组 r 和 S 的元组 s 才能成功地组成一对。如果在 连接RS中元组r和s成功 地匹配 成对, 那么, 成对的结 果就是一 个元 组, 称 为连 接元 组, 其 中每 个分量都对应于R和S的模式并集中的一个属性。连接元组在R的 模式的每 个属性 上和元 组r一 致, 而在S的 模式 的每·521·
个属性上和元组 s 一致。因为 r 和 s 成功地组成一对, 我们就知道 r 和 s 在 R 和 S 的模式的公共属性上一致。因此, 连接元组在属于两个模式的公共属性上与r和s都一致是可能的。连接元组的建立可以通过图 4. 4表示出来。另外还要注意, 这个连接运算和我们在 3. 7. 6 节中使用的连接运算是一样的, 在那里用连接运算重新组合已经投影到其属性的两个子集上的关系。其动机是解释 BCNF 分解为什么有意义。在 4. 1. 7 节, 我们将看到自然连接的另一个用途: 连接两个关系以使我们能够写出与每个关系的属性都有关的查询。例 4. 6 在图 4. 3 中, 关系R和S的自然连接是A B C D1 `2 _5 ^6 ]3 `4 _7 ^8 ] R和S唯一的公共属性是B。因此, 要成功地匹配成对, 元组只需要在B分量上一致即 可。如果这样, 结果元组有属性 A( 来自 R) 、B( 来自 R 或 S) 、C( 来自 S) 和 D( 来自 S) 对应的分量。在这 个例子 中,R的 第一 个元 组只和S的第 一个 元组成 功地 匹配; 在它 们的 公共 属性 B 上它们有公共值 2。这一对产生了第一个结果元组: ( 1, 2, 5, 6) 。R 的第二个元组只和S的第二个元组成功地匹配, 这一对产生的元组是( 3, 4, 7, 8) 。注意,S的第三个元组不能和 R 的任何元组匹配。因此对 R S 的结果没有影响。没能在连接中和另一个关系的任何元组匹配的元组有时称为悬挂元组。 □例 4. 7 前面的例子并不能说明为自然连接运算符所特有的所有可能性。例如: 没有一个元组成功地和多个元组匹配, 并且两个 关系模式只有一 个公共属性。在图 4. 5, 我们看到另外两个关系 U 和 V, 在它们的模式中, 有两个公共属性 B 和 C。我们还给出了一个元组和几个元组连接的实例。A B C1 2 Q3 "6 7 Q8 "9 7 Q8 "关系UB C D2 _3 04 2 _3 05 7 _8 010 关系VA B C D1 2 3 4 /1 2 3 5 /6 7 8 10 F9 7 8 10 F结果UV图 4. 5 关系的自然连接·621·
为了元组成功匹配, 它们必须在 B 和 C 的分量都一致。因此, U 的第一个元组成功地和V的前两个元组匹配, 而U的第二个和第三个元组成功地和V的第三个元组匹配。这四对的结果在图 4. 5 中给出。 □4. 1. 6 θ连接自然连接要求我们使用一个特定的条件匹配元组。虽然公共属性相等这种方式是关系相连的最普通的基础, 但有时希望把两个关系的元组按其他某个基础匹配成对。为此,我们有一个相关的表示法, 称为θ连接,θ指一个任意的条件。但在表达式中, 我们用C而不是θ表示它。R和S基于条件C的θ连接用RCS表示。该运算的结果按如下步骤建立:1. 获得R和S的积。2. 从乘积中只选择满足条件C的元组。因为使 用了乘积 运算, 结果模 式是 R 和 S 模式的 并集, 如果需要 指出 属性来 自哪 个模式, 则可在属性前面加上前缀“R. ”或“S. ”。例 4. 8 考虑运算 U A < D V, 其中, U 和 V 是来自图 4. 5 的关系。我们必须考虑由每个关系的一个元组组成的所有 9 个元组对, 并要看U元组的A分量是否小于V元组的D分量。U 的第一个元组, A 分量为 1, 和 V 的每个元组成功地匹配成对。然而, U 的第二和第三个元组, A 分量分别为 6 和 9, 只和 V 最后的元组成功地匹配成对。于是, 结果只有 5个元组, 由 5 个成功的匹配构造而成。该关系在图 4. 6 中给出。 □注意, 图 4. 6 中的结果模式包 括所有 6 个属性, 并用U和V作 为前缀加在它们 相应的属性B和C的前面以区别它们。于是,θ连接与自然连接形成对照, 因为后者的公共属性合并成一个副本。当然, 在自然连接的情况下这样做是有意义的, 因为如果两个元组不在它们的公共属性上一致, 那它们就不能组成一对。在 θ连接的情况下, 不能保证在 结果中相比较的属性一致, 因为此运算符可能不是“= ”。A U.B U.C V.B V.C D1 2 3 v2 G3 4 1 2 3 v2 G3 5 1 2 3 v7 G8 106 7 8 v7 G8 109 7 8 v7 G8 10图 4. 6 UA< D V的结果例 4. 9 这里是相同关系U和V的θ连接, 具有更加复杂的条件:UA < D AN D U. B ≠ V. BV也就是说, 对于成功 的匹配, 不仅 要求U元组 的A分量小 于V元 组的D分 量, 而 且要求两个元组在它们相应的B分量上不一致。·721·
等价表达式和查询优化所有的 数据库系 统都有查 询-应 答系统, 其中 许多都基 于一种 在表达 能力上 与关系代数类似的语言。因此, 用户提出的查询可能有许多等价表达式( 当给定相同的关系作为运算数将产生同一个答案的表达式) , 其中某些计算可能更快。1. 2. 3 节简要讨论的查询优化的一个重要工作, 就是用计算更有效的等价表达式取代关系代数的一个表达式。元组A U. B U. C V. B V. C D1 2 I3 7 8 t10 -是满足两个条件的唯一元组, 因此该关系是上述 θ连接的结果。 □4. 1. 7 查询中 的复合运 算当查询时, 如果我们所做的一切就是在一个或两个关系上写单一的运算, 那么关系代数不会像它现在那样有用。然而, 关系代数像所有代数一样, 允许我们通过将运算符用于给定关系或者结果关系从而形成任意复杂的表达式。所谓结果关系就是将一个或多个运算符用于关系得到的结果。可以通过将运算符用于子表达式, 必要时用括号表明运算项分组, 来构造关系代数表达式。把表达式表示为表达树也是可能的; 后者对我们来说读起来往往比较容易, 不过表达树作为机器可读的表示法不是太方便。例 4. 10 让我们重新考虑例 3. 32 分解的电影关系。假定我们想知道“由 Fox 公司制作的至少有 100 分 钟 长 的 电 影 的 名 称 和 年 份 是 什 么?”, 要计算这个查询的答案, 一个途图 4. 7 关系代数表达式对应的表达树径是:( 1) 选择长度length≥100 的Movie元组。( 2) 选择 StudioName= ' Fox' 的 Movie 元组。( 3) 计算( 1) 和( 2) 的交集。( 4) 将来自( 3) 的关系投影到属性 title 和 year 上。我们在图 4. 7 看到表示以上步骤的表达树。两个选择节点相当于步骤( 1) 和( 2) 。交集节点相当于步骤( 3) , 而投影节点是步骤( 4) 。作为另一种方法, 我们可以用常规的、具有括号的线性表示法来表示相同的表达式。公式πtit le, year ( σl en gt h≥ 10 0 ( Movie) ∩σStud ioName = ' Fox ' ( Movie) )代表相同的表达式。顺便提一下, 经常有多个关系代数表达式代表相同的计算。例如, 用单一的选择运算·821·
中的逻辑与( AND) 代替交集, 也可以写出上述查询。也就是说,πtit l e, year (σlength ≥ 1 00 AND S tu dioN am e = 'F ox' (Movie) )是等价的查询形式。 □例 4. 11 自然连接运算的一个用途是重组被分解的 关系, 分解 的目的是使关系 成为BCNF。回忆一下例 3. 32 中分解的关系:①Movie1 具有模式{tit le, year, length, filmType, studioName}Movie2 具有模式{tit le,year,st arName}让我们写一个表达式来回答查询“找出主演的电影至少 100 分钟长的影星”。该查询将Movie2 的starName和Movie1 的lengt h属性相连。我们可以通过连接这两个关系来连接这些属性。自然连接只把那些在 tit le 和 year 上一致的元组成功地匹配成对; 也就是把指向同一部电影的元组匹配成对。因此,Movie1 Movie2 是关系代数的一个表达式,它产生例 3. 32 中我们称为 Movie 的关系。该关系是非 BCNF 关系, 它的模式是所有 6 个属性, 当一部电影有几个影星时它将包含涉及该电影的几个元组。对于 Movie1 和 Movie2 的 连接, 我们 必须 使用一 个选 择, 其条件 是要 求电影 的长 度至少是 100 分钟。然后, 我们投影到所要求的属性st arName上。表达式πs tarName ( σlen gt h≥ 10 0 ( Movie1 Movie2) )用关系代数表达了所要求的查询。4. 1. 8 改名为了调整由一 个或多个关系代 数运算构 造的关系 所用的属 性名, 使用显 式的把关 系改名的运算符往往是很方便的。我们将使用运算符ρs(A1 ,A2 , …,An ) (R) 把关系R改名。结果关系和 R 有完全相符的元组, 但关系名是 S 。此外, 还把结果关系 S 的属性从左至右依次命名为A1 ,A2 , …,An 。如果我们只想把关系改名为S, 而让属性和R中的一样, 那我们可以只是用 ρS( R) 。例 4. 12 在例 4. 5 中, 我 们产生了图 4. 3 的两个关系R和S的 乘积, 并 按惯例为 属性改名, 若一个属性出现在两个运算数中, 就用关系名作为它的前缀而使之改名。这两个关系R和S在图 4. 8 中重复给出。然而, 假设我们并不希望称 B 的两种描述为名字 R. B 和 S . B; 而想继续对来自 R 的属性使用名字B, 但希望使用X作为来自S的属性B的名字。我们可以把S的属性改名,以便第一个属性称为 X。表达式 ρS ( X , C, D)( S) 的结果 是名为 S 的关系, 看起来和图 4. 3 中的关系S几乎一样, 但是其第一列的属性为X, 取代了原来的B。当我们 求 R 和这 个新关 系的乘积 时, 属性 之间没 有名 字的冲 突, 因此 不必进 一步 改名。也就是说, 除了五列从左到右依次标记为A,B,X,C,D之外, 表达式R×ρS( X , C , D) (S)的结果就是图 4. 3 的关系 R× S。这个关系在图 4. 8 中给出。作为另一种方 法, 我们求 乘积时可以 不改名, 如在 例 4. 5 所 做的那 样, 然 后把结果 改名。表达式 ρR S ( A , B , X , C, D )( R× S) 将产生和图 4. 8 中相同的关系, 具有相同的属性集, 但是关·921·①记住, 例 3. 32 中的关系 Movie 与 3. 9 节介绍的并用于 例 4. 2、4. 3 和 4. 4 的关系 Movie, 其关系模式 稍有不同。
系名为 RS, 而图 4. 8 中的结果关系并非如此。A B1 2 3 4 关系RB C D2 5 6 4 7 8 9 10 11 关系SA B X C D1 2 12 _5 6 1 2 14 _7 8 1 2 19 _10 11 3 4 12 _5 6 3 4 14 _7 8 3 4 19 _10 11 结果 R× ρS(X, C, D )( S)图 4. 8 两个关系及其乘积4. 1. 9 基本和 导出运算在 4. 1 节我们已经讨论过的某些运算可以用其他关系代数运算的形式表达。例如, 交集可以用集合差的形式表达R∩S= R- ( R- S )也就是说, 如果R和S是具有相同模式的任何两个关系, 要计算R和S的交集, 可以首先从 R 中减去 S , 形成的关系 T 包括所有在 R 中而不在 S 中的元组。然后从 R 中减去 T , 就只剩下既在R中也在S中的元组。连接的两种形式也可以用其他运算的形式表达。θ连接可以通过乘积和选择表达。R cS = σc( R× S)R和S的自然连接可以首先用乘积R×S表达。然后应用选择 运算符并带有如 下形式的条件 C 进行选择R.A1 =S.A1AND R.A2 =S.A2AND…AND R.An =S.An其中,A1 ,A2 , …,An 是出现在R和S的两个模式中的所有属性。最后, 我们必须对每个等值属性的一个副本进行投影。假设 L 是一个属性表, 其中前面的属性在 R 的模式中, 后面的属性在S的模式中而不在R的模式中。于是R S= πL ( σc( R× S) )例 4. 13 图 4. 5 的关系U和V的自然连接可以用乘积、选择和投影的形式写出:πA , U. B , U. C, D (σU. B = V. B AND U. C = V. C (U×V) )也就是说, 我们得到乘积U×V, 然后在每对 同名属性( 在这个例子中 是B和C) 之间 进行等值选择。最后, 我们投影到所有的属性上, 但只保留其中一个B和一个C; 我们的选择是·031·
取消 V 的属性, 如果该属性的名字也在 U 的模式中出现的话。对于另一个例子, 例 4. 9 的θ连接可以写作σA < D AN D V. B ≠ V. B ( U× V)也就是说, 我们求关系 U 和 V 的乘积, 然后应用出现在 θ连接中的条件。 □在本节提到的冗余只是我们已经介绍的运算之间的“冗余”。剩下 6 个运算 —— 并集、差集、选择、投影、乘积和改名 —— 构成独立集, 其中每个都不能由其他 5 种形式导出。4. 1. 10 本节练习练习 4. 1. 1: 在这 个练习 中, 我 们介绍关 系数 据库模 式的 一个 不断滚 动的 实例和 某些 采样数据。数据库模式包括四个关系, 它们的模式是:Product ( maker, model, type)PC( model, speed, ram, hd, cd, price)Laptop( model, speed, ram, hd, screen, price)Printer( model, color, type, price)Product关系给出不同产品的制造商、型号和类型 (PC、便携式电脑或 打印机) 。 为了方便, 我们假定型号对于 所有制造商和产 品类型 是唯一的; 这个 假设并 不现实, 实际的 数据库将把制造商代码作为型号的一部 分。PC关 系对于每个PC型号给出速 度( 处理 器的速度, 以兆赫计算) 、RAM 的容量 ( 以 兆字 节计 算) 、硬盘 容量 ( 以 G 字节 计算) 、光 盘驱 动器的速度( 例如, 4 倍速) 和价格。便携式电脑( Laptop) 关系和 PC 是类似的, 除了屏幕尺寸( 用英寸计算) 记录在原来记录 CD 速度的地方。打印机( Printer) 关 系对于每台打印 机的类 型 记 录打 印机 是否 产生 彩 色输 出( 真, 如 果是 的 话) 、工艺 类 型 ( 激 光、喷 墨 或干 式 ) 和价格。关系Product的某些采样数据在图 4. 9 中给出。其他三个关系的采样数据在图 4. 10中给出。制造商和型号均已“妥善处理”, 但是数据取自 1996 年底出售的典型产品。maker( 制造商)model( 型号)type( 类型)maker( 制造商)model( 型号)type( 类型)A 1001 个人电脑( PC)A1002 个人电脑A 1003 个人电脑B 1004 个人电脑B1006 个人电脑B3002 打印机B 3004 打印机C1005 个人电脑C1007 个人电脑D 1008 个人电脑D1009 个人电脑D1010 个人电脑D 2001 便携式电脑D2002 便携式电脑D 2003 便携式电脑D 3001 打印机D3003 打印机E2004 便携式电脑E 2008 便携式电脑F2005 便携式电脑G2006 便携式电脑G 2007 便携式电脑H3005 打印机I3006 打印机图 4. 9 产品的采样数据·131·
写出关系代数的表达式, 回答下列查询。对于图 4. 9 和 4. 10 中的数据, 给出你的查询结果。然而, 你的答案应该适用于任意数据, 而不仅是这些图中的数据。model( 型号) speed( 速度) ram( 内存) hd( 硬盘) cd( 光驱) pr ice( 价格)1001 1133 H16 _1 H. 6 6 X1595 1002 1120 H16 _1 H. 6 6 X 1399 1003 1166 H24 _2 H. 5 6 X1899 1004 1166 H32 _2 H. 5 8 X1999 1005 1166 H16 _2 H. 0 8 X 1999 1006 1200 H32 _3 H. 1 8 X2099 1007 1200 H32 _3 H. 2 8 X2349 1008 1180 H32 _2 H. 0 8 X 3699 1009 1200 H32 _2 H. 5 8 X 2599 1010 1160 H16 _1 H. 2 8 X1495 (a) 关系PC( 个 人电 脑) 的采样 数据model( 型号) speed( 速度) ram( 内存) hd( 硬盘) screen( 屏幕) pr ice( 价格)2001 1100 H20 _1 1. 10 9 v. 5 1999 2002 1117 H12 _0 1. 75 11 . 3 2499 2003 1117 H32 _1 1. 00 11 . 2 3599 2004 1133 H16 _1 1. 10 11 . 2 3499 2005 1133 H16 _1 1. 00 11 . 3 2599 2006 1120 H8 H0 1. 81 12 . 1 1999 2007 1150 H16 _1 1. 35 12 . 1 4799 2008 1120 H16 _1 1. 10 12 . 1 2099 ( b) 关系 L aptop( 便携 式电脑 ) 的 采样 数据model( 型号)color( 彩色)type( 类型)price( 价格)3001 真 喷墨 275 ]3002 真 喷墨 269 ]3003 假 激光 829 ]3004 假 激光 879 ]3005 假 喷墨 180 ]3006 真 干式 470 ]( c) 关系 Printer ( 打印机 ) 的 采样 数据图 4. 10 练习 4. 1. 1 中各关系的采样数据提示: 对于比较复杂的表达式, 利用给定的关系( Product 等) 定义一个或更多中 间关系, 然后在最终表达式中使用这些关系, 也许是有益的。此后, 可以替换最终表达式中的中间关系, 以得到用给定关系表示的表达式。* (a) 什么型号的PC速度至少为 150?·231·
( b) 哪个厂商生产的便携式电脑具有最少 1G 字节的硬盘。 (c) 找出厂商B生产的所有产品( 任一类型) 的型号和价格。 (d) 找出所有彩色激光打印机的型号。 (e) 找出销售便携式电脑但不销售PC的厂商。* ! (f) 找出在两个或两个以上PC中出现的硬盘容量。! ( g) 找出速度相同且 RAM 相同的成对的 PC 型号。一对型号只列出一次。例如,列出( ( i, j ) 就不再列出( j, i) 。! ! ( h) 找出至少生产两种不同的计算机( PC 或便携式电 脑) 且 机器速度至少为 133的厂商。! ! (i) 找出生产最高速度的计算机(PC或便携式电脑) 的厂商。! ! (j) 找出至少生产三种不同速度PC的厂商。! ! ( k) 找出只卖三种不同型号 PC 的厂商。练习 4. 1. 2: 把练习 4. 1. 1 列出的每个表达式画成表达树。练习 4. 1. 3: 这个 练习介 绍另一个 不断滚 动的 实例, 是有 关第 二次世 界大 战中的 主力 舰的。它用到以下关系:Classes(class,type,country,numGuns,bore,displacement)Ships(name,class,launched)Batt les(name,date)Outcomes(ship,battle,result)舰艇都是按具有相同设计的“等级”制造的, 而等级总是以该等级的第一艘舰艇命名。关系 Classes 记录 class( 等级名) 、type( 类型: bb 代 表战列舰, 或 bc 代表巡洋舰 ) 、count ry( 制造舰艇的 国家) 、numGuns( 主要 火炮的 数量) 、bore( 主 要火炮 的口径: 炮管的 直径, 以英寸计 算) 和displacement( 排 水量: 重量, 以 吨 计算 ) 。关 系Ships记 录name( 舰 艇名 ) 、class( 舰艇等级名) 和launched( 舰艇下水的年份) 。关系Battles给出涉及这些舰艇的战役的name( 名字) 和date( 日 期) , 关 系Outcomes给出 每 艘舰 艇 在每 次 战役 中 的result( 结果: 沉没、损坏或完好) 。图 4. 11 和 4. 12 给出这四个关系的某些采样数据。①注意, 和练习 4. 1. 1 中的数据不同, 在这些数据中有 某些“悬 挂的元组”, 例如, 在Outcome中提到而 在Ships中没有提 到的舰艇。写出关系代数的表达式回答下列查询。对于图 4. 11 和图 4. 12 的数据, 给出你的查询结果。然而, 你的答案应该适用于任意的数据, 而不仅是这些图中的数据。(a) 对于配备至少 16 英寸口径火炮的等级, 给出等级名和国家。( b) 找出 1921 年以前下水的舰艇。( c) 找出在北大西洋战役中沉没的舰艇。(d) 1921 年的华盛顿条约禁止主力舰的重量在 35 000 吨以上。列出违背华盛顿条约的舰艇。·331·①来 源:J.N.Westwood,Fighting Ships of World War II,Follett Publishing,Chicago, 1975and R.C.Ster n,U S Ba ttleships in A ction, Squadr on/ Signal P ublications, Car rollton, T X, 1980。
class type country numGuns bore displacementBismarck bb 德国 8 15 42000 -Iowa bb美国 9 16 46000 -Kongo bc日本 8 14 32000 -North Carolina bb 美国 9 16 37000 -Renown bc大不列颠 6 15 32000 -Revenge bb大不列颠 8 15 29000 -Tennessee bb 美国 12 14 32000 -Yamoto bb日本 9 18 65000 -( a) 关 系 Cla sses 的采 样数 据name dataNor th Atlantic5 f/ 24-27/ 41Guadalcanal 11 / 15/ 42North Cape12 / 26/ 43Surigao Strait10 / 25/ 44( b) 关系 Battles 的采样 数据ship( 舰艇)battle( 战役)r esult( 结果)Bismarck North Atlantic沉没California Surigao Strait完好Duke of York North Cape 完好Fuso Surigao Strait沉没Hood North Atlantic沉没King George V North Atlantic 完好Kirishima Guadalcanal沉没Prince of Wales North Atlantic毁坏Rodney North Atlantic 完好Scharnhorst North Cape 沉没South Dakota Guadalcanal毁坏T ennessee Surigao Strait 完好Washington Guadalcanal 完好West Virginia Surigao Strait完好Yamashir o Surigao Strait沉没(c) 关系Outcomes的采样数据图 4. 11 练习 4. 1. 3 的数据( e) 列出参与瓜达尔卡纳尔岛战役的舰艇的名字、排水量以及火炮的数量。(f) 列 出数据库中提到 的所有主力 舰( 记 住, 可 能不是 所有的舰 艇都出 现在Ships关系中) 。·431·
用惊叹号( ! ) 标出。最难的练习用双惊叹号( ! ! ) 标出。有一些练习或练习的某些部分标有星号( * ) 。对于这些练习, 我们将尽量通过本书的主页提供解答。这些解答是公开的, 并可用于自我检测。注意: 在某些情况下, 练习 B 要求您对另一个练习A的解答进行修改或改进。如果A的某些特定部分有解答, 那么B的相应部分也将有解答。万维网上的支持本书的主页是:http: / / www-db. stanford. edu/~ullman/ fcdb. html这里有带星号的练习的解答, 对已发现的书写或印刷错误的勘误表以及辅助教材。我们希望 每一个像 我们一 样讲授 CS145 课程 的人都 能获 得这 些课程 笔记, 包 括课外 作业、解答和工程项目的作业。致 谢特别感 谢Bobbie Cochrane和Linda DeMichiel, 感 谢他们 在SQL3 标 准方面 给予 的帮助。还有其他许多人帮助我们审校手稿, 他们是: Donald Aingworth, Jonathan Becker,Larry Bonham,Christopher Chan,Oliver Duschka,Greg Fichtenholtz,Bart F isher,Meredit h Goldsmith, St eve Huntsberry, Leonard Jacobson, T hulasiraman Jeyaraman,dwight Joe,Seth Katz,Brian Kulman,Le-Wei Mo,Mark Mortensen,RamprakashNarayanaswami, Torbjorn Norbye, Mehul Patel, Catherine T ornabene, JonathanU llman,Mayank Upadhyay,Vassilis Vassalos,Qiang Wang,Sundar Yamunachari和T akeshi Yokukawa。当然, 剩下的错误由我们负责。J. D. U.J.W.··
name class launchedCalifornia Tennessee 1921 FHaruna Kongo1915 FHiei Kongo1914 FIowa Iowa 1943 FKirishima Kongo1915 FKongo Kongo1913 FMissour i Iowa 1944 FMusashi Yamato1942 FNew Jersey Iowa1943 FNorth Carolina North Carolina 1941 FRamillies Revenge 1917 FRenown Renown1916 FRepulse Renown 1916 FResolution Revenge 1916 FRevenge Revenge1916 FRoyal Oak Revenge1916 FRoyal Sovereign Revenge 1916 FTennessee Tennessee1920 FWashington North Carolina1941 FWisconsin Iowa 1944 FYamato Yamoto1941 F图 4. 12 关系 Ships 的采样数据! ( g) 找出那些其成员只有一个的等级。! (h) 找出既有战列舰又有巡洋舰的国家。! (i) 找出那些“来日再战斗”的舰艇。它们在一次战 役中受损, 但以后又投入另 一次战役。练习 4. 1. 4: 将你在练习 4. 1. 3 中写的每个表达式画成表达树。* 练习 4. 1. 5: 自然连接 R S 和 θ连接 R C S ( 对于出现在 R 和 S 的模式中的每 个属性 A, 条件 C 是 R. A= S. A) 有什么不同?! 练习 4. 1. 6: 如果把一个元组加到某个关系运算符的一个自变量中, 得到的结果将 包括在增加该元组前它所包含的所有元组, 还可能加上更多的元组, 则认为该关系运算符是单调的。在这一节描述的什么运算符是单调的? 对于每个运算符, 或者说明它为什么是单调的, 或者给出一个表明不是的例子。! 练习 4. 1. 7: 假定关 系R和S分别有n个元组 和m个元 组, 给出下列 表达式的 结果中可能的最小和最大元组数* (a)R∪S(b)RS(c)σC (R)×S, 其中C为某个条件。·531·
( d) πL ( R ) - S, 其中 L 为某个属性表。! 练习 4. 1. 8: 关系R和S的半连接记为RS。它是在R和S的模式的所有公共属性上, 至少和一个 S 元组一致的 R 元组的集合。给出三个不同的与 R S 等价的关系代数表达式。! ! 练习 4. 1. 9: 假设 R 是 模式为( A1 , A2 , …, An , B1 , B2 , …, Bm ) 的关 系, 并 且假设 S 是 模式为(B1 ,B2 , …,Bm ) 的关系。也就是说,S的属性是R的属性的子集。R和S的商, 表示为R÷ S, 是属性 A1 , A2 , …, An ( 即 R 属性中不是 S 属性的部分) 上满足如 下条件的元组 t 的集合, 条件为对于S中的每个元组s, 由t对应于A1 ,A2 , …,An 的分量 和s对应于B1 ,B2 ,…, Bm 的分量组成的元组 ts, 都是 R 的成员。用本节前面定义的运算符写出与 R÷ S 等价的关系代数表达式。4. 2 关 系 的 逻 辑另一种表达关系查询的方法基于逻辑而非代数。有趣的是, 两种方法( 逻辑和代数) 可以表达相同类型的查询。这一节介绍的逻辑查询语言称为 Datalog(“数据逻辑”) , 它是由if-then规则组成 的。我们用这些规则之 一表达这样的想 法: 从某 些关系中的 某些元组 组合出发, 可以推测出某个其他元组在某个其他关系中, 或在查询的答案中。4. 2. 1 谓词和 原子关系在Datalog中通过称为谓词的符号表示。每个谓词都有固定数量的参数, 而谓词及其随后的参数称为原子。原子的语法很像传统编程语言中的函数调用。例如P(x1 ,x2 ,…,xn ) 是由谓词P和参数x1 ,x2 , …,xn 组成的原子。本质上, 谓 词是返回一个布尔 值的函数名。如果R是以某种固定 顺序排列的n个属性的关系, 那么, 我们也应当用 R 作为相应于这个关系的谓词的名字。如果( a1 , a2 , …, an )是R的一个元组, 则原子R(a1 ,a2 , …,an ) 的值为T RU E; 否则原子的值为FALSE。例 4. 14 假设 R 是图 4. 3 中的关系A B1 `2 ^3 `4 ^那么 R( 1, 2) 为真, 并且 R( 3, 4) 也为真。然而, 对于任何其他的 x, y 值, R( x, y) 为假。 □谓词可以用变量以及常量作为参数。如果一个原子的一个或多个参数为变量, 那么它就是一个布尔值函数, 该函数随变量取值不同而取真或假。例 4. 15 如果R是例 4. 14 中的谓词, 那 么对于任 意的x和y,R(x,y) 是指 出元 组( x, y) 是否属于关系 R 的函数。对于例 4. 14 中 R 的具体实例, 当1.X= 1 且y= 2, 或者2. X = 3 且 y= 4·631·
时, R( x, y) 返回真, 否则返回假。作为另一 个例子, 当 z= 2 时, 原子 R( 1, z) 返回真, 否则返回假。 □4. 2. 2 算术原 子Datalog 中, 另一种重 要的原子是算术 原子。这种 原子是两 个算术 表达式的 比较, 比如x<y或x+ 1 ≥y+ 4×z。与此相对, 我们将把 4. 2. 1 节介绍的原子称为关系原子; 这两者都是“原子”。注意, 算术原子和关系原子都以任意变量的值为参数, 并返回布尔值。在效果上, 算术比较( 比如< 或≥) 就像某个关系名, 其关系包括比较结果为真的所有的“对”。于是, 我们可以把关系“< ”具体化, 成为包括( 1, 2) 或( - 1. 5, 65. 4) 在内的所有元组, 其第一个 分量小于第二个分量。然而, 数据库的关系总是有限的, 并且通常随时间变化。而算术比较关系( 例如< ) 则是无限的, 并且不变。4. 2. 3 Datalog 规则和查询类似于关系代数中的那些运算在 Datalog 中通过规则描述。规则包括:1. 一个称为头部(head) 的关系原子, 随后是2. 符号←, 通常读作“if”, 随后是3. 由 一个或多个原子 组成的体(body) , 其原子称为子 目标(subgoal) , 它可以是关 系的, 也可以是算术的。各子目标用 AND 连接, 并且, 子目标前面可以有逻辑运算符 NOT ,也可以没有。例 4. 16 Datalog 规则LongMovie(t,y) ←Movie(t,y,l,c,s,p)AND l≥100可以用来计算至少 100 分钟长的“长电影”。它引用我们在 3. 9 节定义的标准关系 Movie,其模式为Movie( title, year, length, inColor , studioName, producerC# )规则的头部是原子LongMovie(t,y) 。规则体包括两个子目标:1. 第一个子目标有谓词 Movie 和 6 个参数, 对应于 Movie 关系的 6 个属性。这些参数中的 每一个都 有不 同的值:t对 应于tit le分 量,y对 应于year分量,l对应 于length分量, 等等。我们 可以将这个子目标 看作是在表明:“假设( t, y, l, c, s, p) 是关系 Movie 的当前实例 中的元 组。”更 确切地 说, 当 6 个变量 具有 某一 个Movie元 组的 6 个 分量的 值时,Movie( t, y, l, c, s, p ) 为真。2. 当Movie元组的长度分量至少为 100 时第二个子目标l≥100 为真。规 则 作 为 一 个 整 体 是 在 表 明: 当 可 以 在 Movie 中找 到 符 合 下 列 条 件 的 元 组 时,longMovie(t,y) 为真:( a) t 和 y 为前两个分量( 对应于 tit le 和 year) ,(b) 第三个分量l( 对应于lengt h) 至少为 100, 并且( c) 分量 4 至 6 为任意值。因此, 这个规则等价于关系代数中的“赋值语句”:·731·
匿名变量通常,Datalog规则 中某 些变 量只 出现 一次。因 此, 这 些变 量的 名字 是 无关 紧要的; 仅当 一个变量多次出 现时, 我 们才关注它 的名字 以便将其 视为第 二次以及 后来出现的同一个变量。于是, 我们按常规以下划线“- ”作为原子的参数, 代表只出现在那里的变量。多条下划线“- ”代表不同的变量, 决不是相同的变量。例如, 例 4. 16 的规则可以写为longMovie( t, y) ← Movie( t , y, l, - , - , - ) AND l≥100每个只出现一次的变量c,s和p都用下划线替代。我们不 能替代其他变量, 因为它们在规则中都出现了两次。longMovie=πtit l e, year (σlength ≥ 1 00 (Movie) )它的右边是关系代数表达式。 □Datalog 中的 查询是 一个 或多 个规则 的聚 集。如果 规则 头部 只有 一个 关 系出 现, 那么, 就认为该关系的值是查询的答案。因此, 在例 4. 16 中, longMovie 就是查询的答案。如果规则头部有多个关系, 那么这些关系中的一个是查询的答案, 而其他关系在答案定义中起 辅助 作用。我 们必须 指定 哪个关 系是 查询 要求的 答案, 可 以通过 给出 一个名 字( 例 如Answer) 来指明这个关系。4. 2. 4 Datalog规则的含义例 4. 16 给出一个Datalog规则含 义的提示。更确切 地说, 想象 一下在所有可能 值范围内变化的规则变量。当这些变量都具有使所有子目标为真的值时, 那么, 我们对于这些变量检查头部的值是什么, 并且将结果元组加入谓词在头部的关系中。例如, 我们可以想象例 4. 16 的在所有可能值范围内变化的 6 个变量。只有(t,y,l,c,s, p ) 的值按顺序构 成一 个 Movie 元组, 才是 使所 有子 目标 为 真的 值的 组合。 并且, 因 为l≥100子目标必须也为真, 所以这个元组必须是长度分量的值 l 至少为 100 的元组。当我们找到这样的值的组合时, 就将元组(t,y) 放入头部的关系LongMovie中。然而, 我们必须对规则中 变量的使用 方法加 以限制, 以使规 则的结 果是有限 关系, 并且使得带算术子目标或求反 子目标( 前面有 NOT 的子目 标) 的规 则具有直观意义。 这个条件称为安全条件, 即:· 出现在规则中任何地方的变量必须出现在某个非求反的关系子目标中。特别要指出的 是, 在头部、求反关 系子目 标或任何 算术子目 标中出 现的变量, 也必 须出现在非求反的关系子目标中。例 4. 17 考虑例 4. 16 的规则LongMovie( t, y) ←Movie( t , y, l, - , - , - ) AND l≥100第一个子目标是非求反的关系子目标, 并且它包括出现在规则中的所有变量。特别要注意的是, 出现在头部中的两个变量t和y也出现在体的第一个子目标中。变量l不但出·831·
现在算术子目标中, 而且也出现在第一子目标中。例 4. 18 以下规则有三处违反了安全条件:P(x,y) ←Q(x,z)AND NOT R(w,x,z)AND x<y1. 变量 y 出现在头部, 但是没有出现在任何非求反的关系子目标中。注意, y 出现在算术子目标x<y中的事实 无助于将y的可能 值限制在有限集中。 只要我们找到分 别对应于 w, x 和 z 的值 a, b 和 c 满足前两个子 目标, 数量 无限的元组( a, d) ( 其中 d> a) 就 会对头部的关系P“纠缠不休”。2. 变量w出现在求反的关系子目标中而不在非求反的关系子目标中。3. 变量 y 出现在算术子目标中, 但不在非求反的关系子目标中。因此, 它不是安全的规则, 不能用于Datalog。 □还有另一种方法定义规则。不考虑对变量所有可能的赋值, 而考虑和每个非求反的关系子目标相对应的关系中元组的集合。如果对于每个非求反的关系子目标的元组的某个赋值在这样的 意义上是一致的, 即对于一 个变量 的每个出 现赋予 同一个值, 那么, 就考 虑把结果的值赋予规则的所有变量。注意, 因为规则是安全的, 所以对每个变量赋予一个值。对于每个一致的赋值, 我们考虑求反的关系子目标和算术子目标, 以便检查对变量的赋值是否使它们都为真。记住, 如果求反子目标的原子为假, 则求反子目标为真。如果所有的子目标都为真, 那么, 我们将检查对变量的这种赋值使头部变成了什么元组。这个元组将加入其谓词是头部的关系中。例 4. 19 考虑 Datalog 规则:P(x,y) ←Q(x,z)AND R(z,y)AND NOT Q(x,y)假设关系Q包括两个元组( 1, 2) 和( 1, 3) 。假设关系R包括元组( 2, 3) 和( 3, 1) 。这里有两 个非求反的关系 子目标, Q( x, z) 和 R( z, y) , 所以, 我们必 须考虑 分别来自 关系 Q 和R的元组对这些子目标赋值的所有组合。图 4. 13 考虑了所有的四种组合。Q(x,z) 的元组R(z,y) 的元组 赋值一致?NOT Q(x,y) 为真? 头部的结果( 1 I) ( 1 , 2) ( 2 , 3) Yes No -( 2 I) ( 1 , 2) ( 3 , 1)No;z= 2 ¥, 3 不相关 -( 3 I) ( 1 , 3) ( 2 , 3) No; z= 3 ¥, 2 不相关 -( 4 I) ( 1 , 3) ( 3 , 1)Yes Yes P( 1 , 1)图 4. 13 元组对 Q( x , z) 和 R( z, y) 的所有可能的赋值图 4. 13 中第二和第三种选择方案不一致。每种都把两个不同的值给变量 Z。这样, 我们就不能进一步考虑这些元组赋值。第一种选 择 方 案, 对 子 目 标 Q( x, z) 赋 予元 组 ( 1, 2) 并 对 子 目 标 R ( z, y) 赋 予 元 组( 2, 3) , 产生了一致的赋值, x, y 和 z 分别赋值 1, 3 和 2。于是, 我们进入对其他子目标的测试, 即对不是非求反的关系子目标的测试。只有一个:NOT Q(x,y) 。对于变量的这 种赋值, 这个子目标变成 NOT Q( 1, 3) 。因为( 1, 3) 是 Q 的一个元组, 这个子目标为假, 对于元组赋值选择方案( 1) 没有产生头部元组。·931·
最终的选择方案为 ( 4) 。这 里, 赋 值是 一致 的; x, y 和 z 分 别赋 值 1, 1 和 3。 子目 标NOT Q(x,y) 取值为NOT Q( 1, 1) 。因为元组( 1, 1) 不是Q的元组, 故该子目标为真。于是, 我们按变量的这种赋值计算头部 P ( x, y) , 得到 P ( 1, 1) 。元组( 1, 1) 在关系 P 中。我们已经详细讨论了所有元组赋值方案, 因此这是P中唯一的元组。4. 2. 5 外延和 内涵谓词对以下两者进行区别是有益的:· 外延谓词, 其关系存储在数据库中。· 内涵谓词, 其关系通过应用一个或多个 Dat alog 规则计算而求得。两者的差别和关系代数表达式的运算项( 它们是外延的, 也就是, 由它们的外延定义,它是关系的当前实例的另 一个 名 字) 与用 关 系代 数表 达式 计算 的 关系 ( 或 者 作为 最终 结果, 或 者作 为对 应于某 个子 表达式 的中 间结 果; 这 些关 系是“内 涵的”, 即由编 程者 的“意图”定义) 之间的差别相同。说 到Datalog规则 时, 如 果谓 词分 别是“内涵 ”或“外延 ”的, 那么, 我 们 将引 用与“内涵”或“外延”谓词相对应的关系。我们也将用“内涵 数据库”的缩 写 IDB 来引 用内涵谓 词或相应关系。与此类似, 我们把“外延数据库”的缩写EDB用于外延谓词或相应关系。这样, 在例 4. 16 中, Movie 是一个 EDB 关系, 通过它的外延定义。谓词 Movie 同样是一个EDB谓词。关系和谓词LongMovie都是内涵的。EDB 谓词永远不能出现在规则的头部, 不过, 它可以出现在规则体中。IDB 谓词可以出现在规则的头部或者体中, 或者两者兼有。通过使用在头部具有相同谓词的几个规则,构造单个的 关系也是常见的。我 们将在涉及 两个关系 并集的例 4. 21 看到对 这个概念 的解释。我们可以用一系列的内涵谓词逐渐建立 EDB 关系的更复杂的函数。这个过程类似于用几个运算符建立关系代数表达式。我们在下一节还将看到使用几个内涵谓词的例子。4. 2. 6 本节练 习练习 4. 2. 1: 用 Datalog 写出练习 4. 1. 1 的每个查询。应当只使用安全规则, 但是你 可能希望使用与复杂的关系代数表达式的子表达式相对应的几个IDB谓词。练习 4. 2. 2: 用 Datalog 写出练习 4. 1. 3 的每个查询。仍然只使用安全规则, 但是如 果愿意, 也可以使用几个IDB谓词。! ! 练习 4. 2. 3: 如果关系子目标的谓词具有有限关系, 那么, 给予 Datalog 规则安全的要求足以保证头部谓词有一个有限关系。然而, 这个要求太严了。给出违背这个条件的Datalog的一个例子, 而不管我们赋予关系谓词的有限关系是什么, 头部关系将是有限的。4. 3 从关系代数到Datalog每个关系代数运算符都可以用一个或几个 Datalog 规则来模拟。在这一节, 我们将依次考虑每个运算符, 然后, 考虑如何对Datalog规则进行组合以模拟复杂的代数表达式。·041·
规则中的变量是局部的注意, 我 们为规则中的变 量所选择 的名字 是任意的, 并且 和其他 任何规则 中使用的变量没有联系。没有联系的原因是每个规则单独计算, 并且为与其他规则无关的头部关系提供元组。例如, 我们可以用U( w, x, , z) ←S( w, x, y, z)替代例 4. 21 中的第二个规则而第一个规 则保持不变, 这两个规则 仍将算出R和S的并集。然而要注意, 当用变量 a 替代写在规则中的另一个变量 b 时, 我们必须用 a 替代b在规则中的所有出现, 并且, 我们选择的替换变量a一定不是规则中已有的变量。4. 3. 1 交集两个关系的交集可用一个规则来表示, 该规则具有与两个关系对应的子目标, 并对相应的参数使用相同的变量。例 4. 20 以图 4. 1 中的关系 R 和 S 为例。回忆一下这些关系, 每个关系的模式都具有四个属性:name,address,gender和birthdate。这样, 它们的交集用Datalog规则计算I( n, a, g, b) ←R( n, a, g, b) AND S( n, a, g, b)其中, I 是一个 IDB 谓词, 当我们应用这个规则时, 它的关系变成 R∩S 。也就是说, 对于某个元组(n,a,g,b) , 为了使两个子目标都为真, 该元组必须同时在R和S中。 □4. 3. 2 并集两个关系的并集要用两个规则来构造。每个规则都有一个与其中一个关系相对应的原子作为它唯一的子目标, 并且, 两个规则的头部都有相同的在头部的 IDB 谓词。每个头部的参数都和它的规则子目标中的完全相同。例 4. 21 为了得到例 4. 20 中关系 R 和 S 的并集, 我们使用两个规则( 1) U( n, a, g, b) ←R( n, a, g, b)( 2)U(n,a,g,b) ←S(n,a,g,b)规则( 1) 表明, R 中的每 个元组 都是 IDB 关系 U 中的 一个元 组。类似 地, 规则 ( 2) 表明,S中的每个元组都在U中。这样, 两个规则合在一起意味着R∪S中的每个元组 都在U中。如果我们在头部没有写U的更多的规则, 那么, 任何其他元组都没有进入关系U的途径, 在这种 情况下, 我 们可以得出结论, U 正好是 R∪S。①回忆 一下, 因 为关系是集合,所以, 一个元组即使同时出现在R和S中, 在关系U中也只出现一次。 □4. 3. 3 差集关系R和S的差集用具有求反子 目标的单一规则 计算。也就是说, 非求反子目 标是·141·①实 际上, 我们 应假 设, 在本节 的每 个例子 中, 除了 显式地 给出 的规则 之外 , 没 有其他IDB谓 词规则 。如 果有其他规则 , 那 么就 不能把 其他 元组排 斥在 该谓词 的关系 之外 。
谓词 R, 求反子目标是谓词 S。这些子目标和头部对于相应的参数都有相同的变量。例 4. 22 如果R和S是例 4. 20 中的关系, 那么规则D( n, a, g, b) ←R( n, a, g, b) AND NOT S( n, a, g, b)将D定义为关系R—S。 □4. 3. 4 投影为了计算关系R的投影, 我们使用一个具有谓词R的单一子 目标的规则。该子 目标的参数是不同的变量, 每个变量对应于关系的一个属性。头部有带参数的原子, 这些参数是按要求的顺序与投影的属性表对应的变量。例 4. 23 假定我们想把关系Movie(tit le,year,length,inColor,st udioName,produceC# )投影到它的前三个属性—— title, year 和 length 上, 正如例 4. 2 那样, 规则P(t,y,l) ←Movie(t,y,l,c,s,p)的作用就是定义一个称为 P 的关系作为投影的结果。 □4. 3. 5 选择在Datalog中要表达选择比较困难。简单的 情况是选择条件为 一个算术比较或 多个算术比较的“与”( AND) 。在这种情况下, 我们建立一个具有如下子目标的规则:1. 一个关系子目标, 对应于将对其进行选 择的关系。该原子对 于每个分量有不 同的变量, 而每个分量都对应于关系的一个属性。2. 算术 子目标, 每个算 术子目 标对应于 选择条 件中的一 个比较, 且与 这个比 较是 等同的。虽然在选择条件中使用属性名, 但在算术子目标中却使用相应的变量, 并与关系子目标中建立的变量保持一致。例 4. 24 例 4. 4 的选择σlen gth ≥ 10 0 AND st u dioN ame = ' Fox ' ( Movie)可以写成如下的Datalog规则S(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND l≥100AND s=' F ox'结果是关系 S。注意, l 和 s 是对应于属性 length 和 st udioName 的变量, 而这些属性则按Movie中属性的标准顺序排列。 □现在, 让我们来考虑涉及条件的 OR( 或) 的选择。我们不必用单一 Datalog 规则替换该选择。但是, 对两个条件OR或的选择等价于对每个条件单独的选择, 然后取结果 的并集。这样, n 个条件的 OR 或可用 n 条规则表示, 其中每条规则都定义相同的头部谓词。第i条规则对n个条件中的第i个进行选择。例 4. 25 让我们用 OR 替换 AND 来修改例 4. 24 的选择, 得到以下结果σlength ≥ 10 0 OR s tudi oName = ' F ox' (Movie)也就是, 找出或者长度 至少 100 或者由Fox公司制作 的所有电影。我们 可以写两 个规则, 每个都对应于下面两个条件中的一个:1.S(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND l≥100·241·
2. S( t , y, l, c, s, p) ←Movie( t, y, l, c, s, p) AND s= ' Fox'规则 1 将找出至少 100 分钟长的电影, 而规则 2 将找出由Fox制作的电影。 □即使 更复 杂 的选 择条 件也 可以 通 过以 任 何顺 序 多次 使 用逻 辑 运 算符 AND, OR 和NOT而形成。然而, 有一个众所周知的技术( 我们将不在这里介绍它) 可以把任何这 样的逻辑表达式重新组成“析取范式”, 即表达式是“合取式”的 OR。合取式是直接项的 AND,而直接项则是比较或者求反的比较。①可以用子目标表示任何直接项, 在它前面可能有NOT。如果是算术子 目标,NOT就可以并入比较运算符。例如,NOT x≥100 可以写成x< 100。于是, 任何合取式都可以用单一Datalog规则表示, 而每个子目标对 应一个比较。最后, 每个析取 范式表达式都可 以用几个 Datalog 规则 写出, 而每个 规则对应 一个合取 式。这些规 则取每 个“合取 式”结果 的“并”, 也就是OR。例 4. 26 我们在例 4. 25 中给出了这个算 法的一个简单例子。 一个更加复杂的 例子可以用例 4. 25 的条件取反形成, 于是我们得到表达式:σNO T ( lengt h ≥ 1 00 OR stud ioName= 'F ox ' ) ( Movie)也就是说, 找出长度小于 100 而又不属于Fox的所有电影。在这里, 把NOT用于本身不是简单比较的表达式中, 因此, 我们必须将NOT下 推到表 达 式 中, 利 用 德 · 摩 尔 根 定 律 ( DeMorgan' s Law) 的 一 种 形 式, 即 OR 的 反 是 反 的AND。也就是说, 选择可以重写为σ( NOT ( length ≥ 100) ) AND ( NOT ( s t udio N ame= ' F ox ' ) ) ( Movie)现在, 我们可以在表达式内部取反, 得到表达式σlen gth < 10 0 AND st u dioName ≠ ' F ox ' (Movie)这个表达式可以转换成 Datalog 规则S(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND l< 100AND s≠' F ox'□例 4. 27 让我们考虑一个类似的例子, 其中的选择含有 AND 的反。现在, 我们利用德·摩尔根定律的第二种形式, 即AND的反是反的OR。我们先从代数表达式开始σNOT ( lengt h ≥ 1 00 A ND stu dioNam e= 'F ox' )( Moive)也就是说, 找出不是既长又出自 Fox 的所有电影。利用德·摩尔根定律(DeMorgan' s law) 将NOT推到AND下面, 可得σ( NOT ( leng t h≥ 100 ) ) OR ( NOT ( s t udi oName= ' F ox ' ) ) ( Movie)再在比较内部取反, 可得σlength < 1 00 OR st ud ioName ≠ ' F ox ' (Movie)最后, 可写出两个规则, 每个规则对应OR的一部分。所得到的Datalog规则为:1. S( t , y, l, c, s, p) ←Movie( t, y, l, c, s, p) AND l< 1002.S(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND S≠' Fox'□·341·①可 参 阅A.V.A ho and J.D.U llman,Foundations of Computer Science,Computer Science Press,NewY or k, 1992.
4. 3. 6 乘积两个关系的乘积 R× S 可以用单一 Datalog 规则表示。这个规则有两个子目标, 一个对应于R, 一个对应于S。这些子目标中的每一个都有不同的变量, 每个变量对应于R或S 的一个属性。头 部的 IDB 谓词把出现 在两个子 目标中 的所有变 量作为 参数, 出现在 R子目标中的变量排列在S子目标的变量之前。例 4. 28 让我们考虑例 4. 20 中的两个四属性关系 R 和 S。规则P(a,b,c,d,w,x,y,z) ←R(a,b,c,d)AND S(w,x,y,z)将P定义为R×S。我们使用任意变量,R的参数对应的变量取自字母表的开始,S的参数对应的变量取自字母表的结尾。这些变量都出现在规则头部。 □4. 3. 7 连接我们可以用很像乘积的规则的Datalog规 则取两个关系的自 然连接。差别是如 果我们想取 R S, 那么要 注意, 对于 R 和 S 中有相 同名字的 属性, 必须使 用相同的 变量, 否则必须使用不同的变量。例如, 我们可以使用属性名本身作为变量。头部是每个变量都出现一次的 IDB 谓词。例 4. 29 考虑模式为R(A,B) 和S(B,C,D) 的两个关系。它们的自然连接可由如下规则定义:J(a,b,c,d) ←R(a,b)AND S(b,c,d)注意, 子目标中使用的变量如何以显而易见的方式与关系R和S的属性相对应。 □我们也可以 用直接的方式将 θ连接转换为 Datalog。回忆一下 4. 1. 9 节, θ连接如 何用带选择的乘积来表示。如果 选择条件是合取 式, 也就是 比较的与(AND) , 那么, 我 们可以简单 地从乘积 对应的 Datalog 规则开 始, 然 后, 加上附加 的算术 子目 标, 每个子 目标 对应于一个比较。例 4. 30 让我们考虑例 4. 9 的关系 U( A, B, C) 和 V( B, C, D) , 当时我们应用 θ连接UA< D A ND U . B ≠ V. BV我们可以用Datalog规则J( a, ub, uc, vb, vc, d) ←U( a, ub, uc) AND V( vb, vc, d) AND a< d AND ub≠vb实现相同的运算。我们使用ub作为与U的属性B相对的变量, 类似地使 用vb,uc和vc。不过, 对于两个关系的 6 个属性用任何 6 个不同 的变量 都行。前两 个子目标 引入两个 关系, 后两个子目标进行两个比较, 这是出现在θ连接条件中的两个比较。 □如果 θ连接的 条件不 是合取式, 那么, 如 在 4. 3. 5 节讨论的 那样, 我们将 把它转换 为析取范式。然后为每个合取式建一个规则, 在这个规则中, 我们从乘积对应的子目标开始,接着加上合取式中每个直接项对应的子目标。所有规则的头部是相同的, 而每个参数对应于进行θ连接的两个关系中的一个属性。例 4. 31 在这个例子中, 我们将对例 4. 30 中的代数表达式作一个简单的修改。AND将由OR取而代之。在这个表达式中没有求反运算, 所以它已经属于析取范式。有两个合·441·
第 1 章 数 据 库 系 统 的 世 界 通过本书, 读者可以学会如何有效地使用数据库管理系统, 包括数据库的设计和对数据库操作的编程。本章主要介绍各种重要的数据库概念。简短的历史回顾之后, 我们将了解数据库系统与其他软件风格的区别。在这一章里, 还将介绍支持数据库及其应用的数据库管理系统的实现背景。如果我们要正确评价为什么要设计各种各样的数据库、为什么要对数据库的操作有所限制, 对其“内幕”的理解是非常重要的。最后, 我们将回顾一些读者可能比较熟悉, 但对后续章节而言又必不可少的思想, 比如“面向对象的程序设计”等。1. 1 数据库系统的发展数据库是什 么呢? 实质上, 数据库 只不过 是一些存 在了很 长时间 ——常 常是许多 年—— 的 信 息的 聚集。通 常 意义 下,“数据 库”这 个术 语 是指 由 数 据库 管 理系 统 ( databasemanagement system, 简称为DBMS, 或称为数据库系统) 管理的数据聚集。一个数据库系统应该是:1. 允许用户用一种叫做数据定义语言(data definition language) 的专用语言, 建立新的数据库和指定它们的模式( schema) ( 数据的逻辑结构) 。2. 使用户能够用适当的 语言查询数据(“查询”(query) 是一个数据库 术语, 指对 数据的某种询 问 ) 和 更 新数 据, 所使 用 的 语 言 通常 称 为“查 询语 言 ”或“数 据操 作 语 言”( datamanipulation language) 。3. 支 持存储大 量的数 据—— G( 吉, 109) 字 节以上 ——经 过很长一 段时 间以后, 仍 保证安全, 使其免遭意外或非授权的使用, 同时允许对数据库查询和更新的有效访问。4. 控制 多用户的 同时访 问, 使 得一个用 户的访 问不影响 其他用户, 保 证同时 访问 不会损坏数据。1. 1. 1 早期的 数据库管 理系统第一批商用数据库管理系统出现在 20 世纪 60 年代后期。它们由文件系统演变而来。文件系统能 满足以上第 3 项 的要求 —— 长 时间地存 储数据, 并且 能存储大 量的数 据。但是, 如 果没有备份的话, 文件系统 通常并不能 保证数据 不丢失; 如果 不知道数 据所在的 特定文件, 文件系统也不能支持有效的数据访问。另外, 文件系统并不能直接满足第 2 项关于查询语言对数据的查询要求。它们对第 1项——数据模式的支持也仅限于建立文件的目录结构。最后, 文件系统不支持第 4 项。如果允许多个用 户或进程对文件 进行并发访问, 由于 文件系统 一般不 能避免两 个用户几 乎·1·
取式, 每个都有单一的直接项。表达式是:UA< D OR U . B ≠ V. BV使用和例 4. 30 中一样的变量命名模式, 我们将得到两个规则:1. J( a, ub, uc, vb, vc, d) ←U( a, ub, uc) AND V( vb, vc, d) AND a< d2.J(a,ub,uc,vb,vc,d) ←U(a,ub,uc)AND V(vb,vc,d)AND ub≠vb每个规则都有与所涉及的两个关系对应的子 目标, 以及 与两个条件 A< D 或 U. B≠V. B中的一个对应的子目标。 □4. 3. 8 用Datalog模拟多重运算Datalog 规 则不 仅可 以模拟 关系 代数的 单一 运算, 实际 上还 可以 模拟 任 何代 数表 达式。方法就是检 查关系代 数表达式 对应的表 达树, 并为树 的每个内 部节点 建立一个IDB谓词。每个 IDB 谓词对应的一个或几个规则是把运算符用于树的相应节点所需要的。作为外延的树的运算项( 也就是, 它们是数据库中的关系) 由相应的谓词表示。本身是内部节点的运算项由相应的 IDB 谓词表示。例 4. 32 考虑例 4. 10 的代数表达式πtit le, year ( σlen gth≥ 100 ( Movie) ∩σst u dioNam e = ' F ox ' ( Movie) )它的表达树在图 4. 7 中给出。我们在图 4. 14 中重复了这棵树。它有四个内部节点,所以我们需要建立四个 IDB 谓词。每个谓词都有一个 Datalog 规则, 图 4. 15 汇总了所有图 4. 14 表达树的规则。最底下 的两个内 部节点 对 EDB 关系 Movie 进行 简单的选择, 所以, 我们可以建立IDB谓词W和X来表示这两个选择。图 4. 15 的规则 1 和 2 描述这两个选择。例如, 规则1 定义W为长度至少有 100 的电影元组。然后, 规则 3 定义谓词 Y 为 W 和 X 的交集, 使用我们在 4. 3. 1 节学过的交集对应的规则形式。最后, 规则 4 定义Z 为 Y 在 属 性 tit le 和 year 上的 投 影。在 这里, 使用 了 在4. 3. 4节所学的模拟投影的技术。谓词Z是“回答”谓词; 也就 是说, 由Z定义的关系 和本例开始的代数表达式的结果相同, 而与关系Movie的值无关。1.W(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND l≥ 1002.X(t,y,l,c,s,p) ←Movie(t,y,l,c,s,p)AND s='Fox'3. Y( t, y, l, c, s, p) ←W( t, y, l, c, s, p) AND X( t, y, l, c, s, p) 4. Z( t, y) ←Y( t, y, l, c, s, p)图 4. 15 进行几个代数运算的Datalog规则注意, 在这个例子中, 我们可以用 规则 3 的规则体 取代图 4. 15 的规则 4 中的子目 标Y。再者, 我们可以用规则 1 和 2 的规则体取代W和X子目标。因为Movie子目标都出现 在这 两个 规则 体中, 所以, 我 们可 以取 消一 个 副本。 作为 结 果, 可 以把Z定 义为 单 一规则:Z( t, y) ←Movie( t, y, l, c, s, p) AND l≥100 AND s= ' Fox'·541·
然而, 一个复杂的关系代数表达式与单一的 Dat alog 规则等价, 这种情况并不常见。□4. 3. 9 本节练 习练习 4. 3. 1: 假设 R( a, b, c) , S( a, b, c) 和 T ( a, b, c) 为三个关系。写 出一个或多个定 义下列各个关系代数表达式结果的 Datalog 规则: ( a) R∪S ( b) R∩S (c)R-S* (d) (R∪S) -T ! ( e) ( R - S ) ∩( R- T ) (f)πa, b (R)* ! ( g) πa , b( R) ∩ρU( a, b) ( πb, c( S) )练习 4. 3. 2: 假设R(x,y,z) 为一个关系, 写出一个或多个定 义σC (R) 的Datalog规则, 其中C为以下条件: (a)x=y* (b)x<y AND y<z (c)x<y OR y<z ( d) NOT ( x< y OR x > y)* ! (e)NOT( (x<y OR x>y)AND y<z) ! (f)NOT( (x<y OR x<z)AND y<z)练习 4. 3. 3: 假设R(a,b,c) ,S(b,c,d) 和T(d,e) 为三个关系。对每个自然连接写出 单一的Datalog规则。( a) R S(b)ST! (c) (RS) T( 注意: 因为自然 连接是相关的和 可交换的, 所以这三个关系 的连接顺序无关。)练习 4. 3. 4: 假设R(x,y,z) 和S(x,y,z) 是两个关系。写出一个或多个Datalog规则来定义每个θ连接RCS, 其中,C是练习 4. 3. 2 的条件之一。对于每个条件, 把每个算术比较解释为左边R的属性和右边S的属性的比较。例如,x<y代表R.x<S.y。! 练习 4. 3. 5:Datalog规则可以转换为等价的关系代数表达式。虽然我们还没有给 出一般性的方法, 但你可以给出很多 简单问题的解。对以 下每个 Datalog 规则, 写出关系 代数表达式来定义与规则头部相同的关系:* (a)P(x,y) ←Q(x,z)AND R(z,y)(b)P(x,y) ←Q(x,z)AND Q(z,y)(c)P(x,y) ←Q(x,z)AND R(z,y)AND x<y·641·
4. 4 Datalog 中的递归编程虽然关系代数 可以表达对于关 系有用的 很多运算, 但也 存在某 些不能写 成关系代 数表达式的计算。关系代数不能表达的对数据的一种常见运算涉及一个相似的但是不断增长的代数表达式的非限定序列, 称为递归。例 4. 33 一个取自电影产业的递归运算的例子是有关续集(sequel) 的问题。通常, 每部成功的电影都会制作续集; 如果续集制作得好, 那么续集之后还有续集, 等等。这样, 一部电影可能是很长的系列电影的祖先。假定我们有一个关系 SequelOf( Movie, Sequel) 包含有电影和它的直接续集组成的对。在这个关系中元组的例子是:Movie sequelNaked Gun Naked Gun2 "1/ 2Naked Gun 2 ¥1/ 2Naked Gun 33 91/ 3 我们还可以有 一个 更 普遍 的电 影后 集(follow-on) 的 概念, 表 明是 续集、续集 的续 集等等。在上述关系中, Naked Gun 331/ 3 是 Naked Gun 的一个后集, 但在严格意义上不是我们在这里使用的术语“续集”所指的续集。如果我们在关系中存储直接续集, 而当需要后集时, 再建立后集, 这样就可节省空间。在上述例子中, 我们只少存储一个对, 但是对于五集 的“Rocky”电影我 们将少存 储六个 对, 而 对于 18 集的 电影“F riday t he 13th”则少存 储136 个对。然 而, 我们 如何 从 关系 SequelOf 构造 后集 关系 不 是立 即能 看出 来 的。我 们可 以 把SequelOf和它本身做一次连接来构造续集的续集。可以通过改名使连接成为自然连接, 这样的表达式的例子如下:πfir st , thi rd( ρR( fi rst , secon d) ( SequelOf) ρs ( secon d, t h ird )( SequelOf))在这个表达式中,SequelOf两次改名, 一次改名使其属性称为first和second, 再改名使 其 属 性称 为 second 和 third。这 样, 自然 连 接对 SequelOf 中的元 组 ( m1, m2) 和( m3,m4) 的要求是m2=m3。于是, 将产生元组(m1,m4) 。注意,m4 是m1 续集的续集。与此类 似, 我 们 可 以 连 接 SequelOf 的 三 个 副 本 以 得 到 续 集 的 续 集 的 续 集 ( 例 如,Rocky 和 Rocky IV) 。事实上, 对于任何固定值 i, 通过把 SequelOf 和它本身连接 i- 1 次,就可产生第i个续集。因此, 我们可以取SequelOf和这种连接的有限序列的并集, 以获得直到某个固定限制的所有续集。我们在关系代 数中不能做的是 对每个i给 出第i个 续集的表达式的非 限定序列求 出“非限定并集”。注意, 关系代数的并集只允许我们取两个关系的并集, 并非不限数量。通过把并集运算 符以任何有限的 次数用于代数 表达式中, 就可 以得到 任何有限 数量关系 的并集, 但是在代数表达式中我们从来不能得到非限定数量关系的并集。 □4. 4. 1 固定点 运算符其实, 我们不必为了 表达“相 似”表达式 的非限 定并集而 向关系 代数中增 加杂乱的 约·741·
定。 有 一 个 常 见 的 方 法 来 表 达 通 过 一 个 非 限 定 然 而 正 规 的 过 程 从 其 他 关 系 ( 例 如SequelOf) 建立的关系( 例如FollowOn(x,y) ) ( 也就是, 电影y在例 4. 33 的意义上是 电影x 的一个后集) 。我 们 写 一 个 FollowOn 用 其 本 身 和 SequelOf 描述 的 方 程 式, 然 后 表 明FollowOn的值是满足方程式的最小关系( 最小固定点(least-fixedpoint) ) 。我们将用 符号φ表示要取方程式的最小固定点。例 4. 34 这里是用于描述关系 F ollowOn( x, y) 的方程式最小固定点运算符:φ(FollowOn=ρSequ elOf( x, y) (SequelOf) ∪ρR ( x , y ) (πm ovie, y (SequelOf sequel =x FollowOn) ) )这个 方程 式一 个直 觉的 陈 述是“如 果 电影y是 电影x的续 集或 者y是x续 集的 后集, 那么电影y就是电影x的后集”。为了理解方程式, 我们应当首先注 意FollowOn的属性是x和y。关系FollowOn等于两项的并集。第一项, ρSequ el Of( x , y) ( SequelOf) 是 SequelOf 的一个副本, 经过重命名后它的属性和FollowOn的属性相符。第二项是θ连接SequelOf sequel =x F ollowOn, 它把来自SequelOf的所有的对(a,b) 和来自FollowOn的对(b,c) 相连接。结果是元组(a,b,b,c) , 它的属性分别是 movie, sequel, x 和 y。并集的第二项继续做下去, 首先投影到第一和第四个分量movie和y上, 然后将属性改名为x和y。因此, 按照上面的固定点方程, FollowOn 等 于关系 SequelOf 和 计算续集的后集 的第二项结果的并集。也就是说,FollowOn包含的所有(x,y) 对, 或者在SequelOf中, 或者符合 y 是 x 的续集的后集。换句话说, y 是 x 的续集的续集的续集……, 把“的续集”这几个字用若干次。4. 4. 2 计算最 小固定点然而, 我们 可能不太清楚为什 么根据例 4. 34 的方程式 求出的FollowOn的最小 解恰好是我们所认为的电影后集对的集合。要了解固定点运算符的意义, 就必须了解如何计算最小固定点。在 4. 4. 4 节中, 我们将讨论当差集运算符出现在方程式中时产生的问题, 但是对于没有差集的方程式, 以下方法可行。1. 首先假定方程式左边的关系R为空。2. 通过用R的旧值计算右边, 重复计算关系R的新值。3. 当一次迭代之后, 如旧值和新值相同就停止。例 4. 35 当关系SequelOf包括以下三个元组时, 给出FollowOn的计算过程:movie SequelRocky Rocky IIRocky II Rocky IIIRocky III Rocky IV 在 计 算 的 第 一 次 循 环, 假 定FollowOn为 空。这 样, 在 固 定 点 方 程 中SequelOf和FollowOn 的 连接为空, 而仅有的元组来 自并集的 第一项 SequelOf。于 是, 第 一次循环 之后,FollowOn的值和上述的SequelOf关系相同。第一次循环之后的情况如图 4. 16 所示。·841·
x yRocky Rocky IIRocky II Rocky IIIRocky III Rocky IV图 4. 16 第一次循环之后的关系FollowOn在第二次循环中, 我们用图 4. 16 的关系作为 F ollowOn, 再一次计算固定点方程的右边。并集的第一项SequelOf给出三个已有的元组。对于第二项, 我们必须把关系SequelOf( 它有图 4. 16 所示的 3 个元组) 和当前的关系FollowOn( 它是相同的关系) 连接起来。为此, 我们寻找这样的元组对, 以便来自SequelOf的元组的第二个 分量等于来自FollowOn的元组的第一个分量。这样, 我们可以从SequelOf中得到元组(Rocky,Rocky II) , 把它和来自FellowOn的元组(Rocky II,Rocky III) 匹配成对, 从而为FollowOn得到新的元组(Rocky,RockyIII) 。与此类似, 我们可以从 SequelOf 中得到元组( RockyII, Rocky III) , 并从 FollowOn 中得到元 组(RockyIII,RockyIV) , 从而为FollowOn得到 新的元组 (RockyII,RockyIV) 。然而,没有其他元组对 —— 来自 SequelOf 的元组和来自 FollowOn 的旧值的另一元组—— 能连接起来。于是, 第二次循环之后,FollowOn有 5 个元组, 如图 4. 17 所示。直觉上, 正如图4. 16 只包含这 样的事实: 后集( FollowOn) 基于一个 续集, 而图 4. 17 包 含的事实 是: 后 集(FollowOn) 基于一个或两个续集。x yRocky Rocky IIRocky II RockyIIIRocky III Rocky IVRocky Rocky IIIRocky II Rocky IV图 4. 17 第二次循环之后的关系FollowOn在第三 次循环 中, 我 们为 FollowOn 使 用来自 图 4. 17 的关系, 并 再次 计算固 定点 方程的右边。当然, 由此可得到已有的所有元组和另外一个元组。当我们连接来自SequelOf的元组( Rocky, Rocky II) 和来自 FollowOn 的当前值的元组( Rocky II, Rocky IV) 后, 将得到新的元组( Rocky, Rocky IV) 。这样, 第三次循环之后, FollowOn 的值如图 4. 18 所示。当进入第四次循环时, 并没有得到新的元组, 于是过程结束。用该固定点计算定义的真正关系 FollowOn 如图 4. 18 所示。 □4. 4. 3 Datalog中的固定点方程有用的固定点方程所需要的关系代数 表达式往往会很 复杂。用Datalog规则的 聚集表达它们通常比较容易, 从这一节开始将使用这种表示法。正如我们将在 5. 10 节中所看到的 那样, 在SQL3 中, 用 得更多 的是 代数 的而不 是逻 辑的 固定 点表 示法 来 实现 这些 想·941·
法, 因为这种风格和 SQL 句法更为一致。x yRocky Rocky IIRocky II Rocky IIIRocky III Rocky IVRocky Rocky IIIRocky II Rocky IVRocky Rocky IV图 4. 18 第三次循环之后的关系 FollowOn在逻辑固定点 方程的背后, 一般的想 法首先 是, 假 定一个或 多个关 系其值已 知; 这 些是外延数据库关系, 即EDB关系。其他关系通过出现在规则头部来定义。这些关系是内涵数据库关系, 即 IDB 关系。这些规则的体可能包含 其谓词是 EDB 或者 IDB 关系, 以 及代数原子的子目标。如果一个或多个IDB关系用体中使用相同关系的规则来定义, 那么, 规则用固定点方程就能有效定义这些 IDB 关系, 正如例 4. 34 的关系代数方程中那样。例 4. 36 我们可以用以下两个Datalog规则定义IDB关系FollowOn:1. FollowOn ( x, y) ← SequelOf( x, y)2.FollowOn(x,y) ←SequelOf(x,z)AND FollowOn(z,y)第一个规则是基础, 它告诉我们每个续集都是后集。这个规则对应于例 4. 34 的方程中并集的第一项。第二项规则表明电影 x 续集的每个后 集也是 x 的后 集。更确切地说, 如果 z 是 x 的续集, 并且已经发现y是z的后集, 那么y也是x的后集。 □例 4. 36 的规则正好表明和例 4. 35 的固定点方程同样的事情。因此, 对这些规则计算出 的FollowOn值和 例 4. 35 中 计算 的一 样。一 般 来说, 我 们 可以 用 没 有求 反 子目 标 的Datalog 规则的 任何聚集定义 IDB 关 系, 再从所 有的 IDB 关系为 空开始 计算 IDB 关系 的值, 并通 过把 规 则用 于 EDB 关 系和 IDB 关 系的 以前 值重 复计 算 IDB 关系 的 新值, 直 到IDB关系不再改变。例 4. 37 使用递归的更复杂的例子可以在研究图 的路径问题中找 到。图 4. 19 是一个示意图。它给出了两个假想的航线Untried AirLines(UA) 和Arcane AirLines(AA) 在旧 金山 ( San Francisco) 、丹 佛( Denver) 、达拉 斯( Dallas) 、芝加 哥( Chicago) 和纽 约 ( NewYork) 之间的某些航班。我们可以想象航班用 EDB 关系表示:Flights(airline,from,to,departs,arrives)针对图 4. 19 中的数据, 该关系的元组如图 4. 20 所示。我们 可以提出 的最简 单的递归 问题是“找出 通过乘坐 一个或多 个航班 可以从城 市x到城市y的城 市 对(x,y) ”。以 下 两个 规 则 描 述 的关 系Reaches(x,y) 恰 好包 含 这 些 城市对:1.Reaches(x,y) ←Flights(a,x,y,d,r)·051·
2. Reaches( x, y) ← Reaches( x, z) AND Reaches( z, y)图 4. 19 某些航线的航班图航线 起点站 终点站 起飞时间 到达时间U A SF DEN 930 1230AA SF DAL900 1430U A DEN CHI1500 1800U A DEN DAL1400 1700AA DAL CHI1530 1730AA DAL NY 1500 1930AA CHI NY 1900 2200U A CHI NY1830 2130图 4. 20 关系Flights中的元组第一个规则 表明Reaches包含有 从第一 个城市到 第二个城 市的直 达航班的 城市对;在这个规则中, 航线 a、起飞时间 d 和到达时间 r 是任意的。第二个规则表明, 如果你从城市 x 到达城市 z 并能从 z 到达 y, 那么你就能从 x 到达 y。注意, 我们在这里已经使用了递归的非线性形式, 正如关于“递归的其他形式”的方框中所描述的那样。在这里这种形式多少更方便些, 因为在递归规则中,Flights的另 一个使用将涉及Flights中未用的分量 对应的更多( 三个) 变量。为了计 算关系Reaches, 我 们仿 照 4. 4. 2 节 介绍的 迭代 过程。首 先利 用规则 1 得 到Reaches中的下列城市对: (SF,DEN) , (SF,DAL) , (DEN,CHI) , (DEN,DAL( 译注: 原文误为 CHI) ) , ( DAL, CHI) , ( DAL, NY) 和( CHI, NY) 。这些是图 4. 19 中用弧线表示的7 个城市对。在下一 次循环中, 我们 应用递 归规则 2, 将 这样的 弧线 对放在 一起, 即 一段弧 线的 头是另 一 段 弧 线 的 尾。这 样 做 使 我 们 得 到 附 加 的 城 市 对 ( SF, CHI) , ( DEN, NY) 和 ( SF,NY) 。下一次循环组合这些两段弧线对和所有的单一弧线对共同形成长度最多达到四段弧线的路径。在这个特定的图中, 我们没有得到新的城市对。这样, 关系Reaches包括十个城市对( x, y) , 使得在图 4. 19 的图中可以从 x 到达 y。因为我们画图的方式, 这些城市对·151·
递归的其他形式在 例 4. 34 和 4. 36 中, 我 们 对 递 归 使 用 右 递 归 的 形 式, 其 中 所 用 的 递 归 关 系FollowOn 出现在 EDB 关系 SequelOf 之后。我们也可 以将递归关系放 在开始而写出相似的左递归规则。这些规则是:1. FollowOn( x, y) ← SequelOf( x, y)2.FollowOn(x,y) ←FollowOn(x,z)AND SequelOf(z,y)可 以非 正 式 地 说, 如 果 y 是 x 的 续 集 或 者 x 的 后 集 的 续 集, 那 么, y 就是 x 的后集。我们甚至可以两次使用递归关系, 正如在非线性递归中那样:1.FollowOn(x,y) ←SequelOf(x,y)2. FollowOn( x, y) ← FollowOn( x, z) AND FollowOn( z, y)可以非正式地说, 如果y是x的续集或者x的一个后集的后集, 那么,y就是x的后集。所有这三种形式都为关系 FollowOn 给出相同的值: 对( x, y) 的集合, 其中 y 是x的续集, ……( 若干次续集) 的续集。碰巧正是这样的( x, y) , 使得在图 4. 19 中 y 在 x 的右边。 □例 4. 38 当两个航班可以合并成一个更长的航班序 列时, 更复 杂的定义要求第 二个航 班离 开机 场的 时间 在 第 一个 航 班到 达 该机 场 之后 至 少 一小 时。现 在, 使 用 一个 称 为Connects(x,y,d,r) 的IDB谓 词, 它表 明我 们可 以乘 坐 一个 或多 个航 班, 起 飞的 城市 为x、时间为d, 到达的城市为y、时间为r。如果有任何的连接, 那么至 少有一个小时建 立该连接。连接的规则是:①1.Connects(x,y,d,r) ←Flights(a,x,y,d,r)2.Connects(x,y,d,r) ←Connects(x,z,d,t1)AND Connects(z,y,t2,r)AND t1< =t2 - 100( 译注: 原文误为t2+ 100)在第一次循环中, 规则 1 给我们的结果是图 4. 21 中所示的 8 个连接( Connect) 。每个连接对应于图 4. 19 中所表明的一个航班。注意, 该图的 7 条弧线中, 有一条表示不同时间的两个航班。现在, 我们试着使用规则 2 合并这些元组。例如, 这些元组中的第二个和第五个共同给 出 元 组 (SF,CHI, 900, 1730) 。 然 而, 第 二 和 第 六 个 元 组 没 有 合 并, 因 为 到 达 达 拉 斯(Dallas) 的时间是 1430, 而离开达拉斯的时间是 1500, 仅在半小时之后。图 4. 22 表示第二次循环之后的 Connects 元组。虚线以上是来自第一次循 环的原有元组, 而第二次循 环增加的 6 个元组在虚线以下表示。虚线本身不是关系的一部分。在第 三次循 环中, 原 则上必 须将 图 4. 22 中 的所 有元组 对看 作是 规则 2 的体 中两 个Connects元组的候 选者。然而, 如 果两个元组都在 虚线以上, 那么, 在第二次 循环就已 经·251·①这 些规则 仅在 午夜没 有航班 运营 的假定 下有 效。
把它们考虑在内了, 因此, 不会产生以前未见过的 Connects 元组。得到新元组的唯一途径是假设 在规则 2 的体中 使用的两 个Connects元组中 至少有 一个是前 一次循环 中新增 加进来的, 也就是在图 4. 22 的虚线以下。x y d rSF DEN930 1230 SF DAL 900 1430 DEN CHI1500 1800 DEN DAL 1400 1700 DAL CHI 1530 1730 DAL NY1500 1930 CHI NY1900 2200 CHI NY 1830 2130 图 4. 21 关系 Connects 的基本元组x y d rSF DEN 930 1230 SF DAL900 1430 DEN CHI1500 1800 DEN DAL 1400 1700 DAL CHI 1530 1730 DAL NY1500 1930 CHI NY 1900 2200 CHI NY 1830 2130 SF CHI 900 1730 SF CHI930 1800 SF DAL 930 1700 DEN NY1500 2200 DAL NY1530 2130 DAL NY 1530 2200 图 4. 22 第二次循环之后的关系Connects第三次循环仅给我们三个新元组, 这些在图 4. 23 的底部给出。这个图中的两条虚线把第一次循环 的 8 个元组、第二次循环的 6 个附加 元组和来 自第三 次循环的 3 个新元 组分开。第 四次循 环没有新 元组, 至此我 们的计算 就结束了。 这样, 整个 关系 Connects 如图 4. 23所示。4. 4. 4 递归规 则中的求 反有时, 在涉及递归的规 则中还需要 求反。混合 递归和求 反的方 法有安全 的和不安 全的。通常认为只有求反不出现在固定点运算的内部, 使用求反才是合适的。为了了解差别,我们考虑两个递归和求反的例子, 一个是合适的, 另一个是矛盾的。我们将看到当有递归·351·
时, 只有“分层”求反是有用的, 术语“分层”的确切定义将在例子之后给出。x y d rSF DEN 930 1230 SF DAL900 1430 DEN CHI1500 1800 DEN DAL 1400 1700 DAL CHI1530 1730 DAL NY1500 1930 CHI NY 1500 2200 CHI NY1830 2130 SF CHI 900 1730 SF CHI 930 1800 SF DAL 930 1700 DEN NY1500 2200 DAL NY1530 2130 DAL NY 1530 2200 SF CHI 900 2130 SF NY900 2200 SF NY 930 2200 图 4. 23 第三次循环之后的 Connects 关系例 4. 39 假定我们想找出图 4. 19 中的城市对(x,y) , 使得通过UA可以从x飞 到y( 可能通过其他几个城市) , 但是通过 AA 却不可以。我们可以递归定义谓词 UAreaches,正如我们在例 4. 37 定义 Reaches 那样, 只是将我们自己限制在 UA 航班。定义如下:1. U Areaches( x, y) ← Flights( UA, x, y, d, r)2. U Areaches( x, y) ← U Areaches( x, z) AND UA reaches( z, y)与此类似, 我们可以递归定义谓词 AAreaches 为 这样的城市对( x, y) , 即人们仅 利用AA 航班从 x 旅行到 y。定义如下:1. AA reaches( x, y) ← Flights( AA, x, y, d, r)2.AA reaches(x,y) ←AAreaches(x,z)AND AAreaches(z,y)现在, 看一个 简单的 问题, 计算 UAonly 谓词, 它 包含 通过 UA 航班 而不 能通 过 AA航班从x到y的城市对(x,y) , 用非递归规则:UAonly(x,y) ←UAreaches(x,y)AND NOT AAreaches(x,y)这个规则计算UAreaches和AAreaches的集合差。对 于 图 4. 19 的 数 据, 可 以 看 出UAreaches包 含 下 列 城 市 对: (SF,DEN) , (SF,DAL) , (SF,CHI) , (SF,NY) , (DEN,DAL) , (DEN,CHI) , (DEN,NY) 和(CHI,NY) 。这个集合是按照 4. 4. 2 节描述的迭代的固定点过程计算出来的。与此类似, 对这些数据我们可以计 算 出AAreaches的 值: (SF,DAL) , (SF,CHI) , (SF,NY) , (DAL,CHI) , (DAL,NY) 和(CHI,NY) 。当取这些对的集合差时, 我们得到: (SF,DEN) , (DEN,DAL) , (DEN,·451·
同时修改同一 文件的事件发生, 因此, 必然有 一个用户 的修改会 失败, 无法在 文件中反 映出来。早期 DBMS 最重 要的应 用是在那 些数据中 包含很 多小的 数据项 并且 需要许 多查 询和更新的场合。下面是一些应用的实例。飞机订票系统在这里, 每个数据包括如下数据项:( 1) 一个旅客对一次航班的座位预定, 包括分配座位、进餐选择等信息;( 2) 航班信息 —— 出发地和目的地, 起飞和到达的时间, 或飞机已经起飞等等;( 3) 机票信息 —— 票价、要求、有无等。典型的查询是 在某一段时间内 从某个给 定的城市 飞往另一 个城市 的航班, 还有什 么座位可供选 择, 以及机 票价格。典型的数 据更新 包括为旅 客登记 航班, 分配座位, 选择 餐饮。 任何 时刻, 都会 有许 多代理 来访 问数据 的某 些部 分。DBMS 必须 允许 这种“并发 访问”; 还要 避免一些诸如“两个代理同 时分配了同一 个座位”之类 的问题; 当系 统突然崩 溃时, DBMS 还要避免记录的丢失。银行系统数据项包括顾客的姓名、地址、帐号、存款、结余以及顾客与他们的帐号和存款之间的关系, 比如, 谁对那些帐号有签名权。对结余的查询固然不少, 但更多的是针对一次存款或取款所进行的修改。正 如 飞 机 订 票 系 统 一 样, 我 们 希 望 出 纳 员 和 顾 客 通 过 AT M ( Aut omated T ellerMachine, 自动出纳机) 能同时查 询、更新银 行的数据。对同一帐 号能同 时访问, 而不影 响AT M 业务, 这一点是至关重要的。错误是不能容忍的。例如, 一旦钱从 AT M 自动出纳机中弹出, 银行必须记录这项支出, 即使立刻掉电也不例外。正确处理这种操作远不像想象的那么简单, 可以看作是 DBMS 系统结构的重大进展之一。公司记录许多早期应用 都与公司记录有 关, 比 如每次 销售的记 录, 帐 户的应 付或应收 信息, 雇员信息—— 他们的姓名、地址、工资、津贴、税款等等。查询的内容包括打印帐户收入或雇员每周工资之类的记录。每次销售、采购, 每张帐单、收据, 雇员的雇用、解雇、提升等等, 都将导致数据库的更新。从文件系统发展而来的早期的DBMS希望用户把数据想象成很像它存放的样子。这些数据库系统 使用几种不同的 数据模型来描 述数据库 的信息结 构, 其中主要 是基于树 的层次模型和基于图 的网状模型。后者 在 60 年代 末期通过CODASYL①( 数据系统和 语言协会, Committee on Data Systems and Languages)的一份报告进 行了标准化。关于 网状和层次模型, 读者可以参考 2. 7 节, 尽管今天它们已经成了历史话题。·2·①CO DAS YL Data Base T ask Group A pril 1971 Report , A CM, New Y or k.
CHI) 和( DEN, NY) 。这四对的集合就是谓词 UAonly 的值。 □例 4. 40 现在, 让我们考虑一个工作情况不好的抽象的例子。假定我们有一个EDB谓词 R。这个谓词是一元的( 一个参数) , 有一个元组( 0) 。有两个 IDB 谓词, P 和 Q, 也是一元的。它们用以下两个规则来定义:1. P( x) ← R( x) AND NOT Q( x)2.Q(x) ←R(x)AND NOT P(x)可以非正式地 说, 两个 规则告 诉我们, R 中的元素 x 或在 P 中, 或在 Q 中, 但不同 时在两者之中。注意P和Q相互递归定义。当我们在 4. 4. 1 节对递归规则意味着什么下定义时, 我们说需要最小固定点, 也就是作为代数方程使规则为真的最小关系。规则 1 表明, 作为关系,P=R-Q, 而规则 2 表示Q= R- P 。因为 R 仅包括元组( 0) , 所以 P 或 Q 中只能为( 0) 。但是( 0) 在哪里呢? 它不可能两处都不在, 否则方程不满足。比如P=R-Q将意味着= {( 0) }- , 此式为假。如果 假设 P = {( 0) }, 而 Q= , 那么, 我们 确实得到 两个 方程 的解。P = R—Q 成 为{( 0) }= {( 0) }—, 此式为真, 而Q=R-P变成= {( 0) }- {( 0) }, 此式也为真。然而, 我们也可以假设 P = , 而 Q= {( 0) }。这种选择也满足两个规则。这样我们有两个解: 6( a) P = {( 0) } ,Q= (b)P= Q= {( 0) }两个都是最小的, 在某种意义上, 如果我们使任何元组离开任何关系, 那么, 结果关系都不再满足这两个规则。因此, 不能在两个最小固定点(a) 和(b) 之间作出决定, 这样, 就不能回答比如“P ( 0) 为真吗?”这样简单的问题。 □在例 4. 40 中, 我们看到, 当递归和求反太紧密地纠缠在一起时, 通过找出最小固定点来定义递归规则或固定点方程的想法不再有效。可能有多个最小固定点, 并且这些固定点可能互相矛盾。如果用某个其他方法定义递归性求反能更有效就好了, 但遗憾的是, 没有关于这些规则或方程意味着什么的普遍一致的意见。因此, 常规的做法是限制我们自己在递归中分层求反。例如, 在 5. 10 节讨论的关于递归的 SQL3 标准就做了这种限制。下面我们将看到, 当分层求反时, 存在计算一个特 定最小固定点 ( 可能出自 许多这样的固定 点) 的算 法, 该 最小固定 点符合 直觉的关 于规则的 含义。我们如下定义分层的性质:1. 画一个图, 它的节点对应于IDB谓词。2. 如果 在规则的 头部有 谓词 A, 并在 求反子 目标有 谓词 B, 那么 就画一 个从 节点 A至节点B的弧线。用符号“-”标记这个弧线以标明它是求反的弧线。3. 如果规则的 头部有谓词 A, 而 非求反子 目标有 谓词 B, 那么就 画一个 从节点 A 至节点B的弧线。这个弧线不用负号作为符号。如果这个图有一个环包含一个或更多的求反弧线, 那么表明递归没有分层。否则, 就表明图已分层。我们可以用层划分IDB谓词。谓词A的层是从A开始的一条路径上求反弧线的最大数量。如果把递归分层, 那么, 我们可以从它们的最底层开始计算IDB谓词分层的次序。这·551·
个策略将产 生规则的最小固 定点之一。更重要的 是, 由它们 的层隐含的次序 计算 IDB 谓词好像总是有意义的, 并给我们“正确的”固定点。与此相对, 正如我们在例 4. 40 中看到的那样, 即使有许多固定点可以从中选择, 但未分层的递归可能使我们完全没有“正确”的固定点。例 4. 41 例 3. 49 的 谓词 对应 的图如 图 4. 24 所 示。AAreaches 和 UAreaches 在 0层, 因为没有从它们的 节点开始的路径 涉及求反的弧线。UAonly在 1 层, 因 为存在这 样的路径, 它从该节点开始具有一条求反弧线, 但不存在具有多条求反弧线的路径。这样, 在开始计算UAonly之前, 我们必须全面地计算AAreaches和UAreaches。图 4. 24 由分层递归构造的图 图 4. 25 由未分层递归构造的图比较一下我 们为例 4. 40 的IDB谓词构造图 的情况, 这 个图在图 4. 25 中给出。因 为规则 1 具有和求反子目标Q有关的头部P, 所以有一条从P至Q的求反弧线。因为规则2 具有和求反子目标 P 有关的头部 Q, 所以, 在相反方向上, 也有一条求反弧线。于是就有一个求反的环, 表明规则没有分层。 □4. 4. 5 本节练 习练 习 4. 4. 1: 如 果 我们 针 对 图 4. 19 的 图 增加 或 删 除 弧 线, 就 可 能改 变 例 4. 37 的 关 系Reaches、例 4. 38 的关系 Connect s 或例 4. 39 的关系 UAreaches 和 AAreaches 的值。如果做出如下改变, 请给出这些关系的新值。* (a) 增加一条标记为AA1900-2100 的从CHI至SF的弧线;(b) 增加一条标记为UA900-1100 的从NY至DEN的弧线;( c) 同时增加( a) 和( b) 中提到的两条弧线;(d) 删除从DEN至DAL的弧线。练习 4. 4. 2: 写 出Datalog规 则 ( 若 需 求 反, 则 用 分 层 求 反 ) , 以 便 对 例 4. 33 中 后 集(“Follow-On”) 概念的以下几种 修改加以描述。你可 以使用 例 4. 36 中定义 的 EDB 关 系SequelOf和 IDB 关系 FollowOn。* (a) \P(x,y) 意 味着 电 影y是 电 影x的 后集, 但 不是x的续 集( 正 如用EDB关 系SequelOf所定义的) 。 ( b) Q( x, y) 意味着 y 是 x 的后集, 但不是 x 的续集或续集的续集。! (c) JR(x) 意味着电影x至少有两个后集。注意, 两个都可能是续集, 而不是一个是续集, 而另一个是续集的续集。! ( d) S( x, y) 意味着 y 是 x 的后集, 但是 y 最多有一个后集。练习 4. 4. 3:ODL类及其 联系可以用一个 关系Rel(class,rclass,mult) 描 述。其中,mult·651·
给出联系的多重性, 或用 multi( 多) 对应多值联系, 或用 single( 单) 对应单值联系。前两个属性是相关的类, 联系从class至rclass( 相关类) 。例如, 图 4. 26 中给出的关系Rel表示图2. 6 中不断滚动的电影实例的三个 ODL 类。类( Class) 相关类( rclass) 多( mult)Star Movie 多Movie Star多Movie Studio单Studio Movie 多图 4. 26 用关系数据表示 ODL 联系我们也可以将这些数据看成一个图, 其中节点是类, 而弧线从一个类到一个相关类,图 4. 27 用图表示联系弧 线边上 标记 着合适 的多 ( multi) 或 单( single) 。 图4. 27 描绘出图 4. 26 的数据所对应的图。对于 下列各 种 情况, 写 出Datalog规则, 如 果 必须求反, 就用分层求反来表示所描述的谓词。你可以用Rel作 为一 个EDB关 系。给 出 用你 的 规 则 对 图4. 26 的数据进行计算时每次循环的结果。 ( a) 谓 词 P ( class, eclass) , 意味 着 从 class 至 eclass 的 类 对 应 的 图 中 有 一 条 路径。①可以把后面的类想象成是嵌入在class中, 因为在某种意义上它是第一个类的对象的一部分的一部分的……一部分。* ! (b) 谓词S(class,eclass) 和M(class,eclass) 。 第一 个意 味着 在class中 有一 个eclass“单值嵌入”, 也就是, 沿 着从class到eclass的路径每 条弧线都标记 着单(single) 。第二个,M, 意味着在class中有一个eclass的“多值嵌入”, 也就是说, 沿着从 class 到 eclass 的路径至少有一条弧线标记为多( multi) 。 (c) 谓词 Q( class, elcass) 说明说 有一 条从 class 到 eclass 的 路径 但 没有 单值 路径。你可以用本练习中前面定义的 IDB 谓词。4. 5 对关系的约束关系模型提供了一种方法来表示通常的约束, 例如在 2. 5 节介绍的参照完整性约束。实际上, 我们将看到关系代数给我们提供了简便的方法来表示种类繁多的其他约束, 即使函数依赖也可以用关系代数表达, 正如我们在例 4. 44 中看到的那样。约束在数据库编程中十分重要, 我们将在第 6 章讨论SQL数据库系统如何加强在关系代数中表达的相 同种类的约束。·751·①在 本练习 中不 认为空 路径为“路径”。
4. 5. 1 用关系 代数作为 约束语言用关系代数表达式来表达约束共有两种方法。1. 如果R是关系代数表达式, 那么R= 就是一个约束, 它表明“R的值必须为 空”,或相当于,“在R的结果中没有元组”。2. 如果 R 和 S 是关系代数表达式, 那么 RS 就是一个约束。它表明“R 结果中的每个元组也必须在S的结果中”。当然,S的结果可能包括不是由R产生的附加元组。这些表示约束 的方法在它们所 能表示的 内容方面 实际上是 等价的, 但有 时一个或 另一个比较清楚或比较简洁。也就是说, 约束 RS 也可以仅仅写为 R- S= 。原因是, 如果R中的每个元组也在S中, 那么R-S肯定为空。相反地, 如果R-S不包含元组, 那么R 中的每个元组必然在 S 中( 否则它将在 R- S 中) 。另一方面, 第一种形式 的约束, R= , 也可以仅 仅写为 R 。在技术上, 不是 关系代数 表达式, 但是因 为有计 算结果 为的 表达 式, 例如R-R, 所以 用 作为关 系代 数表达式没有什么危害。在随后几节, 我们将看到如何用这两种形式之一表达重要的约束。正如我们将在第 6章看到的, 第一种形式 —— 等于空集 —— 最广泛地用于SQL编程。然而, 如上所述, 如果我们愿意 就按照 集合-包含来进 行思考, 并在 以后把约 束转换为 等于空 集的形式, 一切 悉听尊便。4. 5. 2 参照完 整性约束在 2. 5 节 称为“参照 完整性约束”的一种 普通类型 约束断言, 出 现在一个 环境中的 值也出现在另一个相关的环境中。我们将参照完整性视为联系“有意义”的一个要素。也就是说, 如果对象 或实体 A 与对象或实体 B 相关, 那 么 B 必须实际 存在。例如, 用 ODL 的术语, 如果对象A中的联系实 际上用一个指针 表示, 那么 指针必须不为空, 并且必须 指向一个真正的对象。在关系模型中, 参照完整性 约束看起来有些 不同。如果一个关 系R的一个元组 有个分量值为v, 那么因为我们的设计意图的缘故, 可以期望v将 出现在另一个关 系S的 某个元组的特定分量中。下面的例子将说明如何用关系代数表示关系模型中的参照完整性。例 4. 42 让我们考虑我们的不断滚动的电影数据库模式, 特别是两个关系Movie(tit le,year,length,inColor,st udioName,producerC# )MovieExec( name, address, cert# , networth)我们可能合 理地假定每部电 影的制片人必须 出现在MovieExec关系中。如 果不是,就有点问题了, 这时至少 要让实现关系数 据库的 系统告诉 我们: 我们有 一部电影, 而系 统并不知道它的制片人。更确切 地说, 每个Movie元组的producerC# 分量也必 须出 现在某 个MovieExec元组的 cert# 分量中。因为行政长官由证件号唯一地标识, 这样系统将保证电影的制片人将在电影的行政长官中找到。我们可以用集合-包含表达这个约束:·851·
πp rocd ucerC # ( Movie) πcer t # ( MovieExec)左边表达式的值是出现在 Moive 元组中 producerC# 分量的 所有证件号的集 合。同样地, 右边表达式的值是MovieExec元组的Cert# 分量中所有证 件号的集合。我们 的约束表明前面集合中的每个证件号也必须出现在后面的集合中。随便提一下, 我们可以用空集的等式表达相同的约束:πpr oducer C# (Movie) -πcert # (MovieExec) = □例 4. 43 我们可以类似地表达一个参照完整性约束, 其中所涉及 的“值 ”用多个 属性表示。例如, 我们可能想声明在关系starsIn(MovieT itle,movieYear,st arName)中提到的任何电影也出现在下面的关系中Movie(tit le,year,length,inColor,st udioName,producerC# )在两个关系中电影都用 title-year 对表示, 因为我们一致认为这两个属性中的任 何一个都 不足 以单 独识别 一部 电影。通 过比较 把两 个关系 投影 到合 适的分 量表 上而 产生 的title-year对约束πm ovieTit le, movi eYear ( starsIn) πt it l e, y ear ( Movie) □表达了这样的参照完整性约束。4. 5. 3 附加约 束的例子同 样 的约 束 表示 法 允许 我 们表 达 比 参照 完 整性 多 得多 的 东 西。例如, 我们 可 以 把任何函数依赖表示 成代数约束, 不过这种表 示法比 我们已经 使用的 函数依赖 表示法更 为麻烦。例 4. 44 让我们把关系MovieStar( name, address, gender, birthdate)的函数依赖name → address表示成代数约束。想 法 是 如 果 我 们构 造MovieStar的所 有 元 组 对 (t1 ,t2 ) , 就 一 定 不 能 出 现 一 个 对 在name 分 量 上 一 致 而 在 address 分 量 上 不 一 致。 为 了 构 造 元 组 对, 我 们 使 用 笛 卡 尔(Cartesian) 积, 而为了寻 找违背函数 依赖的元 组对, 我们使 用选择。然 后, 我 们用结果 等于来声明约束。首先, 因为我们取一个关 系和它本身 的乘积, 所以 为了使乘 积的属 性都有名 字, 就 需要 至 少 把 一 个 副 本 改 名。为 了 简 洁, 让 我 们 使 用 两 个 新 名 字, MS1 和 MS2, 以 便 引 用MovieSt ar 关系。于是函数依赖就可以用如下的代数约束来表示:σM S1 . nam e = M S 2. n am e A ND M S 1. addres s ≠ M S 2. addres s (MS1×MS2) = 上式中, 乘积MS1×MS2 中的MS1 是改名运算ρMS 1( name, addres s, gend er , bir th date) ( MovieStar )的简写, 而 MS2 是 MovieStar 的类似的改名。 □我们有时需要的另一种约束是域约束。通常, 域约束就是简单地要求属性的值有特定·951·
的数据类型, 例如整型或长度为 30 的字符串。这些约束不能用关系代数声明, 因为像整型这种类型不是关系代数的一部分。然而, 通常, 域约束涉及到属性所需要的特定值。如果可取值的集合可以用选择条件语言表达, 那么这个域约束就可以用代数约束语言表达。例 4. 45 假定我们 希望规 定MovieSt ar性别属 性的仅有 的合法 值为' F'和' M'。我们可以用如下的代数方法表示这个约束。σgend er ≠ ' F ' A ND gen der ≠ ' M ' ( MovieStar) = 也就是说,MovieStar中gender分量既不等于' F'也不等于' M'的元组集合为空。 □最后, 有某些约束不在 2. 5 节描述的任何一个范畴内。代数约束语言允许我们表达许多新种类的约束。这里是一个例子。例 4. 46 假定我 们希望 要求一个 人必 须至 少有净 资产 $10 000 000 才可以 做电 影制片公司的总裁。这个约束不能归类为域、单值或参照完整性约束。然而我们可以用如下的代数方法表示它。首先, 我们需要用 θ连接把两个关系MovieExec(name,address,cert# ,networth)Studio( name, address, presC# )连起来, 利用的条件是来自st udio的presC# 和来自MovieExec的cert# 相等。该连接把包含制片公司和行政长官的两个元组组合成对, 以使行政长官就是制片公司的总裁。如果我们从这个关系中选择净资产少于$10 000 000 的元组, 那么根据我们的 约束, 就会 有一个必须为空的集合。这样, 我们可以把该约束表达为:σnet W ort h < 10 0000 00 ( studio p res C# = cert # MovieExec) = 表达同一约束 的另一种方法是, 把代表 制片公 司总裁的 证件号 的集合和 代表具有 净资产至少$10 000 000 的行政长官的证件号集合进行比较; 前者必须是后者的子集。包含πpresC # ( studio) πcert # ( σn etwor th ≥ 10 00 0 000 ( MovieExec) )表达了上述想法。4. 5. 4 本节练 习练习 4. 5. 1: 表达对练习 4. 4. 1 的关系( 在这里再次给出) 的下列约束Product( maker, model, type)PC(model,speed,ram,hd,cd,price)Laptop( model, speed, ram, hd, screen, price)Printer(model,color,type,price)可以用包含的形式或者表达式等于空集的形式写出你的约束。对于练习 4. 1. 1 的数据, 指出对你的约束的任何违背之处。 * (a) 处理器速度小于 150 的PC销售价格一定不超过$1500。 ( b) 屏幕 尺寸小于 11 英 寸的便 携式电 脑一定 至少 有 1G 字 节的 硬盘或 者销 售价格一定低于$2000。 ! ( c) PC 的制造商不会同时制造便携式电脑。* ! ! (d)PC的制造商还必须制造处理器速度至少一样快的便携式电脑。·061·
! ( e) 如果便携式电脑的主存比 PC 更大, 那么便携式电脑的价格必然比 PC 更高。练习 4. 5. 2: 用关系代数表达下列约束。约束基于练习 4. 1. 3 的关系:Classes( class, type, count ry, numGuns, bore)Ships( name, class, launched)Batt les( name, date)Outcomes( ship, battle, result )可以用包含的形式或者表达式等于空集的形式写出你的约束。对于练习 4. 1. 3 的数据, 指出对你的约束的任何违背之处。 ( a) 任何等级的舰艇都不会有大于 16 英寸口径的火炮。 ( b) 如果某个等级的舰艇火炮多于 9 门, 那么它们的口径一定不大于 14 英寸。! ( c) 任何等级的舰艇都不会多于两艘。! ( d) 没有一个国家既有战列舰又有巡洋舰。! ! ( e) 多于 9 门火炮的舰艇不可能在和少于 9 门火炮的舰艇的战役中被击沉。练习 4. 5. 3: 用 Datalog 以及关系代数表达约束是可能的。我们写出一个或几个 Datalog规则来定义一个特殊的其值限定为空的 IDB 谓词。用 Datalog 写出下列各个约束。* ( a) 例 4. 42 的约束。( b) 例 4. 43 的约束。( c) 例 4. 44 的约束。( d) 例 4. 45 的约束。( e) 例 4. 46 的约束。! 练习 4. 5. 4: 假定 R 和 S 是两个关系。假定 C 为这样的一个参照完整性约束: 当 R 有一个元组 在特定属 性A1 ,A2 , …,An 上有某 些值v1 ,v2 , …,vn 时, 必定 有S的 一个元 组在 特定属性B1 ,B2 , …,Bn 上有相同值v1 ,v2 , …,vn 。说明如何用关系代数表达约束C。! ! 练习 4. 5. 5: 假定 R 为关系, 并假定函 数依赖 A1, A2, …, An→B 是涉及 R 的属性的 函数依赖。用关系代数写出表明该函数依赖在R中一定成立的约束。4. 6 包的关系运算虽然元组 的集合( 也 就是关系) 是数据的 简单自然 模型, 正如它 可能出现 在数据库 中的那样, 但商业数据库系统很少完全基于集合。在某些情况下, 关系正如它们出现在数据库系统中那样, 允许有重复元组。回忆一下, 如果一个“集合”允许一个成员多次出现, 那么该集合就称为包或多集。在这一节, 我们将把关系作为包而不是集合; 也就是说, 我们将允许 相同 的元 组在关 系中 多次 出现。当 我们指“集 合”时, 意味 着没有 重复 元组 的关 系; 而“包”则意味着可能有( 也可能没有) 重复元组的关系。例 4. 47 图 4. 28 中的关系是元组的包。其中, 元组( 1, 2) 出现三次而元组( 3, 4) 出现一 次。如 果图 4. 28 是集 合-值关系, 我 们 将不 得不 消除 元组 ( 1, 2) 的 两次 出现。 在一 个包-值关系中, 我们的确允许同一元组的多次出现, 但类似于集合, 元组的顺序无关紧要。·161·
A B1 2 3 4 1 2 1 2 图 4. 28 一个包4. 6. 1 为什么 用包?当考虑关系实现的有效性时, 我们可以看到在几种情况下, 允许关系为包而非集合就可以加速对集合的运算。例如, 当进行投影时, 允许结果关系为包就使我们可以独立地处理每个关系。如果我们希望用集合作为结果, 就需要把每个元组通过投影去掉不想要的分量得到的结果 和其他所有已投 影的元组的结 果进行比 较, 以 确保我 们以前未 见到这个 投影。然而, 如果我们允许用包作为结果, 那么就可以简单地对每个元组进行投影, 并将其加到结果中; 而不需要和其他已投影的元组进行比较。A B C1 2 ^5 /3 4 ^6 /1 2 ^7 /1 2 ^8 /图 4. 29 例 4. 48 的包例 4. 48 如果我们允许结果为包而不消除( 1, 2) 的重复出现, 那么图 4. 28 的包 可以是图 4. 29 所示的关系在属性 A 和 B 上投影的结果。如果我们使用关系代数通常的 投影运算符, 从而消除重复, 结果仅为A B1 `2 ^3 `4 ^ 注意 用 包作 为结 果, 虽 然数 据 量 较大, 却 可 以 计算 得 更快, 因 为 不 需要 把 每个 元 组( 1, 2) 或( 3, 4) 与以前产生的元组进行比较。进而, 如果我们对关 系进行投影, 以得到 一个聚合 ( 如 同 5. 5 节 所讨论的 那样) , 例 如“找出图 4. 29 中A的平均值”, 就不 能用集 合模型来 想象投影 的关系。作 为一个 集合,A的平均值为 2, 因为图 4. 29 中 A 的值只有两个 —— 1 和 3, 而它们的平均值为 2。然而, 如果我 们把图 4. 29 中 的A列作为 包( 1, 3, 1, 1) 来处理, 那 么将得到 图 4. 29 的四个 元组 之间 A 的正确平均值 1. 5。 □允许用包作为结果就可以节省时间的另一种情况是, 取两个关系的并集。如果我们计算并集R∪S并坚持用集合作为结果, 那么就必须检查S中的每个元组是否为R的成员。·261·
如果 S 中的某个元 组在 R 中找到了, 那么 S 中的该元 组就不加 入并集 中; 否 则才加入 并集中。然而, 如果我们允许用包作为结果, 那么只要把R和S的所有元组都复制到答案中即可, 而不考虑它们是否出现在两个关系中。4. 6. 2 包的并 集、交集和差集当我们 取两个 包的并集 时, 每个元 组出 现的次 数就 增加 了。也就 是, 如果 R 是一 个包, 其中元组t出现了n次, 而S也是一个包, 其中元组t出现了m次, 那么在包R∪S中,元组t将出现n+m次。注意,n或m( 或两者) 可以为 0。当我们取两个包 R 和 S 的交集时, 如 果在 R 和 S 中元组 t 分别出现 n 和 m 次, 那么在R∩S中元组t将出现min(n,m) 次。当我们计算R—S( 包R和S的差集) 时, 元组t在R—S 中出现 max( 0, n—m) 次。也就是说, 如果 t 在 R 中出现的次数比它在 S 中出现的次数多, 那么在 R—S 中元组 t 出现的次数为它在 R 中出现的次数, 减去它在 S 中出现 的次数。然而, 如果t在S中出现的次数至少和它在R中出现的次数一样多, 那么t就根 本不会出现在 R—S 中。直觉上, t 在 S 中的每个出现都“抵消”R 中的一个出现。例 4. 49 假设 R 是图 4. 28 的关 系, 也 就是, R 是 一个包, 其中 元组( 1, 2) 出 现 3 次、( 3, 4) 出现一次。假设S为包A B1 `2 ^3 `4 ^3 `4 ^5 `6 ^那么包的并集 R∪S 仍为包, 其中( 1, 2) 出现 4 次( 三次对应于它在 R 中的出现, 一次对应于它在S中的出现) ; ( 3, 4) 出现三次, ( 5, 6) 出现一次。包的交集R∩S是包A B1 `2 ^3 `4 ^其中( 1, 2) 和( 3, 4) 各出现一次。也就是, ( 1, 2) 在 R 中出现 3 次, 在 S 中出现一次, 而 min( 3, 1) = 1, 所以( 1, 2) 在 R∩S 中出现一次。类 似地, ( 3, 4) 在 R∩S 中出现 min( 1, 2) = 1次。元组( 5, 6) 在S中出现一次, 但在R中出现 0 次, 在R∩S中出现min( 0, 1) = 0 次。包的差集 R—S 是包A B1 `2 ^1 `2 ^·361·
对集合按包运算设想我们有两个集合R和S。可以把每个集合看作这样一个包: 该包只是碰巧任何元组最多出现一次。假定计算交集 R∩S, 但是我们把 R 和 S 看作包, 并使用包的交集规则。那么我们得到的结果将与把R和S看作集合应得到的结果相同。也就是说,把 R 和 S 看作包, 一个元组 t 在 R∩S 中的出现次数是它在 R 和 S 中出现次数的最小值。因为R和S是集合, 所以t在每个集合中只能出现 0 或 1 次。不管使用包还是集合的交集规则, 我们发现 t 在 R∩S 中最多只能出现一次, 如果它同时在 R 和 S 中, 那么它正好在R∩S中 出现一次。 与此类似, 如果 使用包 的差集规 则比较R—S或S—R, 我们将得到和使用集合规则完全相同的结果。然而, 并的表现却不同, 这取决于我们把R和S看作集合还是包。 如果我们使用包规则计算 R∪S, 即使 R 和 S 是集合, 结果可能并不是集合。特别是, 如果元组 t 在 R和S中都出现, 而且我们对 并集使用包规则, 那么t在R∪S中 将出现两次, 但如果使用集合规则, 那么 t 在 R∪S 中就只出现一次。因此, 当取并集时, 必须特别指明, 我们正在使用并集的包定义还是集合定义。要了解原 因, 注意, ( 1, 2) 在 R 中出现 三次, 而在 S 中出现一次, 所以在 R—S 中它 出现 max( 0, 3—1) = 2 次。元 组( 3, 4) 在 R 中出 现一 次, 而在 S 中 出现 两次, 所以 在 R—S中, 它出现max( 0, 1—2) = 0 次。在R中没有其他元组出 现, 所以 在R—S中就不会 再有其他元组。作为另一个例子, 包的差集S—R是包A B3 `4 ^5 `6 ^ 元组( 3, 4) 出现一次, 因为这是它出现在 S 中的次数 减去它出现在 R 中的次数之差。由于同样的原因, 元组( 5, 6) 在 S—R 中出现一次。结果包在这种情况下碰巧为一个集合。□4. 6. 3 包的投 影我们已经解释了包的投影。正如我们在例 4. 48 所看到的那样, 每个元组在投影过程中独立进行。如 果 R 是图 4. 29 的 包, 当我 们 计 算 包 的投 影 πA , B ( R) 时, 将 得到 图 4. 28的包。如果在投影过 程中消除一个或 多个属性 使得从几 个元组产 生出相 同的元组, 这些 重复元组并不从包投影的结 果中消除。于是, 图 4. 29 关系R的三 个元组( 1, 2, 5) , ( 1, 2, 7)和( 1, 2, 8) 在投影到属性 A 和 B 上之后, 每个都产生出相同的元组( 1, 2) 。在包的结果中,元组( 1, 2) 出现三次, 而在集合的投影中, 该元组只出现一次。·461·
这 些 早 期 的 数 据 模 型 和 系 统 的 一 个 弊 病 是 它 们 不 支 持 高 级 查 询 语 言。例 如,CODASYL查 询语言 允许 用户通 过数 据元 素之间 的指 针图从 一个 数据 元素 跳到 另一 个数据元素。即便查询非常简单, 编写这样的程序也需要相当大的工作量。1. 1. 2 关系数 据库系统在T ed Codd1970 年的那篇著 名的论文①发表以 后, 数据库 系统发生了显著 的变化。Codd 提出数据库系统应为用户提供这样一种观点, 即数据库系统是用一种称为“关系”的表来组织数据的。而在背后, 可能有一个很复杂的数据结构, 以保证对各种查询的快速响应。但与以前的 数据库系统的用 户不同, 关 系数据 库系统的 用户并 不关心数 据的存储 结构, 而是使查询能用很高级的语言来实现, 从而大大提高了数据库开发人员的效率。从第 3 章介绍 关系的基本概念 开始, 本 书的大 部分内容 都将涉 及到数据 库系统的 关系 模 型。 从 第 5 章 开 始, 将 介 绍 基 于 关 系 模 型 的 最 重 要 的 查 询 语 言SQL(StructuredQuery Language, 结构化查询语言) 。然而, 先对关系做一 下简单介 绍有助于 读者对关 系模型的了解, 同时, 我们给出一个SQL的例子, 以便读者了解关系模型如何支持高级 语言的查询, 从而避免在数据库中“指引路径”的细节。例 1. 1 关 系 就 是 表。表 的 各 列 以 属 性 开 始, 属 性 是 列 的 入口。 下 面 是 一 个 名 为Accounts ( 帐 户) 的 关系, 记 录的 是银 行 的帐 户信 息, 包括 accountNo( 帐 号) 、balance( 结余) 和type( 类型) :accountNo balance type12345 1000 . 00 savings67890 2846 . 92 checking… … …表的开始是三个属性: accountNo, balance 和 type。属性下面各行, 称为元组( tuple) 。这里我们具体给出 了两个元组, 当然, 还有许多元 组被省略 了, 其 中每个 元组对应 于银行的 一个帐户。第一个元组中, 帐号为 12345 的帐户上结余为$1 000. 00, 是一个存款帐户; 第二个元组中, 帐号为 67890 的帐户是一个支票帐户, 结余为$2 846. 92。假设我们希望查询 67890 帐号的结余, SQL 的查询语句如下: SELECT lbalance FROM Accounts WHERE accountNo = 67890;再举个例子, 我们可以通过下述语句查询出现赤字的存款帐号: SELECT laccountNo FROM Accounts WHERE type = ’savings’AND balance< 0;·3·①Codd, E . F. A relational model for large shared data banks, Comm. A CM , 13: 6, pp. 377~387.
包的代数定律代数 定律表 示两 个关系 代数 表达 式之间 等价, 而 表达 式的 参数 为代 表 关系 的变量。等价意味着无论用什么关系替代这些变量, 两个表达式都定义相同的关系。一个众所周知的定律就是并集的交换律:R∪S≡S∪R。不管我们认为关系变量R和S是代表集合还是代表包, 该定律碰巧都成立。然而, 另外有些定律当关系代数用传统方式解释—— 关系作为集合—— 时成立, 但是当把关系解释为包时, 并不成立。这类定律的一个简单例子就是并集上集合差的分配律, ( R∪S ) - T ≡( R- T ) ∪( S- T ) 。该定律对集合成立, 而对包却 不成立。要了解为什 么对包不成立, 假定R、S和T各有元组t的一个副本。那么左边的表达式有一个 t, 而右边的表达式则没有。作为集合, 两边都没有t。对包的代数定律的某些探索将出现在练习 4. 6. 4 和 4. 6. 5 中。4. 6. 4 包的选 择为了把选择用于包, 我们把选择条件独立地用于每个元组。由于总是用包, 因此我们并不消除结果中的重复元组。例 4. 50 如果 R 是包A B C1 2 _5 3 4 _6 1 2 _7 1 2 _7 那么包选择σc≥ 6 (R) 的结果是A B C3 4 _6 1 2 _7 1 2 _7 也就是, 除了第一个元组以 外其他所有元组 都符合选择条件。 最后两个元组在 R 中是重复的, 它们中的每一个都包含在结果中。 □4. 6. 5 包的乘 积包的笛卡尔积规则是意料之中的规则。一个关系的每个元组和另一个关系的每个元组匹配成对, 而不考虑 它是否重复。作为 结果, 如果元 组r在关 系R中 出现m次, 元组s在关系 S 中出现 n 次, 那么在乘积 R× S 中, 元组 rs 将出现 mn 次。例 4. 51 假 设 R 和 S 为图 4. 30 所 表示 的包, 那 么乘 积 R× S 包含 6 个 元组, 如 图4. 30(c) 所示。注意, 我们为集合关系提出的关于属性名的常用习惯 可完全一样地用 到包·561·
上。因此, 属于 R 和 S 两个关系的 属性 B, 在乘积中出现 两次, 每次 都用一个关系名 作为前缀。 □A B1 h2 91 h2 9( a) 关 系 RB C2 G3 4 G5 4 G5 ( b ) 关系 SA R.B S.B C1 2 2 3 /1 2 2 3 /1 2 4 5 /1 2 4 5 /1 2 4 5 /1 2 4 5 /(c) 乘积R×S图 4. 30 计算包的乘积4. 6. 6 包的连 接连接包也没有出现意想不到的事。我们把一个关系中的每个元组和另一个关系中的每个元组进行比较, 判断该元组对是否连接成功, 如果成功就将结果元组放在答案中。生成答案时, 并不消除重复元组。例 4. 52 图 4. 30 中所见到的关系 R 和 S 的自然连接 R S 是A B C1 2 _3 1 2 _3 也就是, R 的元组( 1, 2) 和 S 的元组( 2, 3) 相连接。因为在 R 中有( 1, 2) 的两个副本并且在 S 中有( 2, 3) 的一个副本, 所以有两个元组对连接得到元组( 1, 2, 3) 。没有其他来自 R和S的元组连接成功。作为在相同关系 R 和 S 上的另一个例子, θ连接R R . B < S. BS产生包A R.B S.B C1 `2 _4 ^5 ]1 `2 _4 ^5 ]1 `2 _4 ^5 ]1 `2 _4 ^5 ]·661·
连接的计算如 下, R 的元组( 1, 2) 和 S 的元组( 4, 5) 满足连接 条件。由于 每个元组 在各自的关系中都出现两次, 因此连接元组在结果中出现的次数是 2× 2 即 4 次。其他可能的元 组连接 —— R 的 ( 1, 2) 和 S 的( 2, 3) —— 不满足 连接条件, 所以 这种组合 并不出现 在结果中。 □4. 6. 7 包的运 算用于Datalog规则如 果 没 有 求 反 的 关 系 子 目 标, 那 么 也 可 把 计 算 包 的 选 择、投 影 和 连 接 技 术 用 于Datalog规则。大 致的步骤是, 首先对由不 同子目标表 示的关系 进行连 接, 然 后把结果 按算术子目标所包含的内容进行选择, 再把结果按头部的格式进行投影。在每一步, 我们都使用适于包的算法。把 4. 2. 4 节给出的Datalog规则的第二种计算方法一般化, 在概念上比较简单。回忆一下, 该技术涉及到查找每个非求反的关系子目标, 并用该子目标的谓词对应的关系的所有元组替代它。如果通过对每个子目标元组的选择给每个变量一致的赋值, 并且算术子目标全都为真,①那么随着对变量的这种赋值我们就会看到头部变成了什么。结果元组就放在头部关系中。因为我们现在处理的是包, 所以不清除头部的重复元组。而且, 当我们考虑子目标元组的所有组合 时, 一个子 目标对应关系中 出现n次 的元组在与其他 子目标对 应元组的 所有组合形成的合取式中, 将作为该子目标的元组考虑 n 次。例 4. 53 考虑规则H(x,z) ←R(x,y)AND S(y,z)并假设 R 和 S 为图 4. 30 中的关系。子目标的元组赋值一致( 也就是, 每个子目标 的 y 值赋值相同) 的唯一的情况是, 用R的元组( 1, 2) 对第一个子目标 赋值, 并用S的 元组( 2, 3)对第二个子目标赋值。由于( 1, 2) 在R中出现两次, 而( 2, 3) 在S中出现一次, 因此将有两个元组赋值, 对变量的赋值为 x= 1, y= 2 和 z= 3。头部的元组为( x, z) , 每个元组的 赋值均为( 1, 3) 。于是在头部关系H中, 元组( 1, 3) 出现两次, 而没有出现其他元组。也就是说,关系H 1 H 2 1 `3 ^1 `3 ^是用这个规则定义的头部关系, 在这里我们把关系的属性随意命名为 H 1 和 H 2。更概括地 说, 如果 元组( 1, 2) 在R中出现n次, 元组 ( 2, 3) 在S中出 现m次, 那么元组( 1, 3) 将 在H中出现nm次。 □如果一个关系 用几个规则定义, 那么结 果是由 每个规则 产生的 所有元组 的包构成 的并集。例 4. 54 考虑用两个规则:·761·①注 意, 规 则 中不 能 有任 何 求反 的 关系 子 目标 。在 包 的模 式 下, 不 能显 式 地定 义 具有 求 反 关系 子 目 标 的任 意Da talog 规 则。
H( x, y) ← S( x, y) AND x> 1H(x,y) ←S(x,y)AND y< 5定义的关系 H 。假定关系 S 有图 4. 30( b) 的值, 它是B C2 `3 ^4 `5 ^4 `5 ^ 第一个规则将 S 的三个元组都放入 H , 因为这三个元组的第一个分量都大于 1。第二个规则只把元组( 2, 3) 放入H, 因为( 4, 5) 并不满足条件y< 5。于是, 结果 关系H有 元组( 2, 3) 的两个副本, 以及元组( 4, 5) 的两个副本。 □4. 6. 8 本节练 习* 练习 4. 6. 1: 假设PC为图 4. 10(a) 的关系, 并假定我们计算投影πspeed (PC) 。该表达式作为集合, 它的值是什么? 作为包, 它的值又是什么? 什么是这个投影的元组平均值, 当作为集合处理时, 该投影的元组平均值是什么? 作为包, 其平均值又是什么?练习 4. 6. 2: 对投影πh d (PC) 重复练习 4. 6. 1。练习 4. 6. 3: 该练习引用练习 4. 1. 3 的“战列舰”关系。(a) ;表达 式πbor e (Classes) 产 生具 有 各 种 等级 的 火 炮 口 径的 单 列 关 系。对 于 练 习4. 1. 3 的数据, 这个关系作为一个集合是什么? 作为一个包又是什么?! (b) 7写出关系代数表 达式以给出舰艇 ( 不是等级) 的火炮 口径。你的表 达式必须 对包有意义; 也就是, 数值b出现的次数a必须是火炮口径为b的舰艇数。! 练习 4. 6. 4: 某些关系代数定律对作为集合的关系成立, 对作为 包的关系也成立。 解释为什么以下每个定律既对包成立又对集合成立。* ( a) 并的结合律: ( R∪S) ∪T ≡R∪( S∪T )(b) 交的结合律: (R∩S) ∩T≡R∩(S∩T)( c) 自然连接的结合律: ( R S) T ≡ R ( S T )(d) 并的交换律: (R∪S) ≡(S∪R)( e) 交的交换律: ( R∩S) ≡( S∩R)(f) 自然连接的交换律: (RS) ≡(SR)(g)πL (R∪S) ≡πL (R) ∪πL (S) 。其中,L是任意的属性表。* ( h) 交上并的分配律: R∪( S∩T ) ≡( R∪S) ∩( R∪T )(i) 1σC AND D (R) ≡σC (R) ∩σD (R) 。其中,C和D是关于R中元组的任意条件。! ! 练习 4. 6. 5: 下列代数定律对集合成立, 而对包不成立。解释为什么对 集合成立, 并给出它们对包不成立的反例。* ( a) (R∩S )- T ≡R∩( S- T )(b) 并上交的分配律。R∩(S∪T) ≡(R∩S) ∪(R∩T)( c) BσC OR D ( R) ≡ σC ( R) ∪ σD ( R) 。其中, C 和 D 是关于 R 中元组的任意条件。·861·
4. 7 关系模型的其他外延还有其他一些概念和运算, 它们不是正式的关系模型的一部分, 但是出现在实际的查询语言中。在这一节中, 我们提到的运算和概念包括更新关系、计算聚合( 比如对关系按列求和) , 以及定义关系的“视图”或命名的函数。这些运算和概念都出现在数据库语言SQL中, 并将在第 5 章进一步讨论。我们也将在第 8 章对查询语言 OQL 的讨论中看到其中的某些部分。4. 7. 1 更新关系 代数或 Dat alog 在某种 意义上都 是“查询语言 ”, 其中 每一个 都给 我们求 出一 个关系( 即答案) , 也就是某些给定关系的函数。虽然查询很重要, 但是不能改变的数据库不会令人感兴趣。因此, 所有实际的数据库语言都既包括查询数据库的能力也包括更新数据库的能力。至少, 我们需要如下命令。1. 向关系中插入元组;2. 从关系中删除元组;3. 通过改变一个或多个分量修改已有元组。4. 7. 2 聚合关系代数运算符可独立于同一关系中的其他元组而对某些元组进行运算。通常, 我们希望组合单一关系的元组以产生某个聚合值, 也就是以某种方式组合的所有元组的函数。例如, 在不断滚动的电影实例中我们可能希望:· 统计 Movie 关系中提到的不同电影的数量。· 产生一个表格给出每个制片公司制作的电影长度的总和。· 找出具有最大净资产的电影行政长官。于是, 实际的数据库查 询语言允许我 们对关系 中的列 应用聚合 运算符( 主要 是统计、求和、求平均值、求最小值和最大值) 。4. 7. 3 视图我们可以把关系代数 表达式看作一个“程序”, 它计算关系R并 打印结果或另外 产生R 作为结果。然而, 对关系表达式还有另一种解释。我们可以把它看作是定义一个关系的公式, 而在该公式应用于实际的关系之前该关系并未产生。这样的公式在数据库术语体系中称为视图。我们将看到, 通常把视图作为给定的名字, 并用这些名字作为其他关系表达式的参数, 好像视图就是实际的关系。Datalog 规则也说明了查询和视图的区别。回忆 一下, 我 们把用 Datalog 规则定 义的谓词或关系看作是“内涵的”; 也就是说, 它们是一种关系的定义, 而这种关系不需要以“外延的”或存储的形式存在。视图等价于内涵谓词。正如内涵谓词可以在规则体内使用一样,视图可以用在代数表达式中作为参数。同样地, 正如Datalog规则的聚集可以用于由存储·961·
关系组成的数据库, 当需要时, 对视图同样可以计算。4. 7. 4 空值在许多情况下我们必须对元组的分量赋值, 但又不能给出具体的值。例如, 我们可能知道 Kevin Costner 是影 星, 但不 知道 他的 出 生日 期。因 为所 有的 MovieSt ar 元 组都 有birthdate分量, 我们怎么办 呢? 答 案是, 我 们对 该分 量 可以 使用 一个 称为 空 值的 特殊 值NULL。NULL 值在某种意义上就像任何其他值一样。然而, 在其他方面它不是一个值。特别是, 当我们连接两个关系时, 并不认为两个NULL分 量彼此相等。例如, 两个影星 都用NULL 作为他们的 birthdat e 分量的值, 但并不能假定他们的出生日期相同。使用NULL值可以有许多不同的解释。这里是最常见的几种:1. 值未 知: 也 就是说,“我知道 有某个值 属于它, 但不 知道它是 什么。”如同上 面所 讨论的, 未知的出生日期就是一个例子。2. 值不适用:“没有值在这里 有意义。”例如, 如果 MovieStar 关系 具有 spouse 属性,那么未婚的影星将需要用空值对应该属性, 不是因为不知道配偶姓名, 而是因为没有。3. 值 隐 瞒:“我 们 无 权知 道 属 于 这 的 值。”例 如, 一 个 未 列 出 的 电 话 号 码 可 能 作 为NULL出现在phone属性对应的分量中。4. 8 本 章 总 结 关系代数: 这种代数 是关系模型查询 语言的一种重要 形式。它的主要运 算是并、交、差、选择、投影、笛卡尔积、自然连接、θ连接和改名。 Datalog: 这种逻辑的 形 式是 关 系模 型 查询 语 言 的另 一 种重 要 形式。 在Datalog中, 人们写出规则, 在规则中通过由子目标组成的体定义谓词或关系。头部和子目标均为原子, 而原子包括一个用到某些参 数的谓词( 求反是任选的 ) 。可以用 关系代数表达的所有查询都可以用 Datalog 表达。 递归Datalog:Datalog规则也 可以 是递归 的, 即允 许一个 关系 通过 自己来 定义。递归 Datalog 规则的含义是, 所定义关系 元组的最小固定 点、最 小集合, 也就是 使规则头部恰好等于规则体所隐含的内容。 分层 求反: 当递归 涉及 求反时, 最 小固 定点可 能不 是唯一 的, 在某 些情况 下没 有可接受的Datalog含义。因此, 必须禁止在递归内部使用求反, 从而导致对分层求反的需求。对于这类规则有一个( 或许是几个中的一个) 最小固定点是规则普遍接受的含义。 作为 包的关系: 在 商业 数据库 系统 中, 实际上 关系 是包, 其中 相同 的元组 允许 多次出现。对集合的关系代数运算可扩展到包, 但某些代数定律不能成立。 商业系统中 的关系: 除了使用关系的包 模型之外, 商业系统还提供 未出现在关 系代数或Datalog中的运算。这些运算包括对关系中元组的插入、删除和修改, 关系上的聚合, 以及元组的空值。·071·
4. 9 本章参考文献关 系代 数是 关系模 型基 础论 文[ 4] 的 另一个 贡献。逻 辑查 询语言 的来 历 不太 直接。Codd 在关系模型的一篇早期论文[ 5] 中介绍了称为关系演算的前序逻辑形式。关系 演算是 一 种表 达 式 语 言, 非 常 像 关系 代 数, 实 际 上 表 达 能力 等 价 于 关 系代 数, 在[ 5] 中 已 经证明。Datalog受到编程语言Prolog的启发, 看上去更像 逻辑规则。因为它 允许递归, 比关系演算表达力更强。[ 6] 首先把大量的逻辑推导作为查询语言, 而[ 2] 则把这种想法放在数据库系统的环境中。论文[ 8] 最早使用了表达约束的查询。用 分 层 的 方 法 给 出 固 定 点 的 正 确 选 择, 这 一 想 法 来 自 [ 3] , 不 过 用 这 种 方 法 计 算Datalog规则的想法也是[ 1] 、[ 7] 和[ 10] 的独立想法。另外关于分层求反, 关于关系代数、Datalog 和关系演 算之间 的联系以 及关于 Datalog 规则的 计算, 无论是 否求反均 可在 [ 9]中找到。[ 1] 'Apt,K.R. ,H.Blair,and A.Walker,Towards a theory of declarative knowledge,inFoundations of Deductive Databases and Logic Programming ( J. Minker, ed. ) , pp. 89 ~ 148,Morgan-Kaufmann,San Francisco, 1998.[ 2] 'Bancilhon,F.and R.Ramakrishnan,An amateur' s introduction to recursive query-processingstrategies, ACM SIGMOD Intl. Conf. on Management of Data, pp. 16~52, 1986.[ 3] 'Chandra,A.K.and D.Harel,Str ucture and complexity of relational queries,J.Computer andSystem Sciences 25: 1, pp. 99~128.[ 4] 'Codd,E.F. ,A relational model for large shared data banks,Comm.ACM13: 6,pp. 377~387,1970.[ 5] 'Codd, E. F. , Relational completeness of database sublanguages, in Database Systems ( R.Rustin,ed. ) ,Prentice Hall,Englewood Cliffs,NJ, 1972.[ 6] 'Gallaire, H. and J. Minker, Logic and Databases, Plenum Press, New York, 1978.[ 7] 'Naqvi,S. ,Negation as failure for first-order queryies,Proc.Fifth ACM Symp.on Principles ofDatabase Systems, pp. 114~122, 1986.[ 8] 'Nicolas,J. -M. ,Logic for improving integrity checking in relational databases,ActaInformatica, 18: 3,pp. 227~253, 1982.[ 9] 'Ullman, J. D. , Pr inciples of Database and Knowledge-Base Systems, Volume I, ComputerScience Press,New York, 1988.[ 10] UVan Gelder, A. , Negation as failure using tight derivations for general logic programs, inFoundations of Dedutive Databases and Logic Programming(J.Minker,ed. ) ,pp. 149 ~176,Morgan-Kaufmann,San Francisco, 1988.·171·
第 5 章 数 据 库 语 言SQ L 最常用的关系数据库系统通过称为 SQL( 可以读做“sequel”) 的 语言对数据库进 行查询和更新。SQL的含义是“结构化查询语言(Structured Query Language) ”。尽管SQL的许多重要的特征已经超越了关系代数的范畴, 例如聚合运算( 如求和、统计) 以及数据库更新等, 但是,SQL的重要核心是和关系代数等价的。目前有许多不同版本的 SQL 语言。首先, 存在两个不同的主要标准: ANSI( AmericanNational Standards Instit ute, 美 国国家 标准 协会) 的SQL和 1992 年 采用 的 修正 后的 标准, 称为 SQL-92 或 SQL2。还有一种正在制订的标准称为 SQL3, 它在 SQL2 的基础上扩展了许多新的特性, 如递归(recursion) 、触发(trigger) 以 及对象(object) 等。其次, 还 存在数据库管理系统的主要供货商所开发的不同版本的 SQL。这些版本都支 持最初的 ANSI标准。它们还在很大程度上遵循新近的SQL2 标准, 尽管各自都有一些超越了SQL2 的变化和扩展, 包含了前面提到的 SQL3 标准的某些特性。在本章及随后的两章中, 我们将讨论作为数据库查询语言的SQL的使用。本章 集中讨论 SQL 的通用查询接口。也就是说, 当坐在终端前发出数据库查询命令或更新请求时,我们把SQL看成是独立于操作系统的查询语言。对查询的回答, 将在终端上显示出来。在本章和随后 两章所讨 论的 SQL 中, 我 们通常将 遵循 SQL2 标准, 着 重分析存 在于几乎 所有的商业系统以及早期ANSI标准中的特性。在某些情况下, 如果SQL2 标准不能充分地适用于所讨论的问题, 我们将遵循可以得到的最新的正在制订的 SQL3 标准。本章和随后两章的意图是对于“SQL是什么”这样一个问题给读者提供感性的认识, 更多的是从“教学”的角度而不是从“手册”的角度来进行描述。因此我们只集中于讨论 SQL 最常用的一些特性。关于语言及其“方言”的更多的细节在引用的参考书中可以找到。5. 1 SQL 的简单查询或 许SQL中 最 简 单 的 查 询 就 是 从 某 个 关 系 中 查 找 满 足 某 种 条 件 的 一 些 元 组( tuple) 。这种查询类似于关系代数中的选择。这种简单的查询, 同几乎所有的 SQL 查询一样, 使用了具有SQL特性的三个关键字:SELECT、FROM以及WHERE。例 5. 1 在本例和随后的例子中, 我们会用到在 3. 9 节描述过的数据库模式。回顾一下, 这些关系模式如图 5. 1 所示。在 5. 7 节, 我们将看到如何用SQL来表述模式信息, 但是现在, 让我们假设在 3. 9 节所提及的每个关系和每个域在 SQL 中都有其相应的位置。作为我们的第一个查询, 询问在关系Movie( title, year, length, inColor, studioName, producerC# )·271·
中由 Disney 制片公司在 1990 年制作的所有电影。在 SQL 中, 我们这样表示:SELECT*FROM MovieWHERE studioName= ’Disney’AND year= 1990;这个查询语句展示了大多数SQL查询语句特有的select-form-where格式。 ?Movie(title,year,length,inColor,studioName,producerC# )StarsIn( movieTitle, movieYear, starName)MovieStar(name,address,gender,birthdate)MovieExec(name,address,cert# ,netWorth)Studio( name, addr ess, presC# )图 5. 1 数据库模式的实例( 复制的)· FROM 子句给 出了 查询 所涉及 的关 系。在我 们的例 子中, 是 对关系 Movie 进 行查询。· WHERE子 句给出 了查询的 条件, 这很像 关系代数 中的选 择条 件, 要与查 询相 匹配则选出的元组必须满足这个条件。在这里条件就是, 元组的 studioName 属性取值为 ’Disney' , 元组的 year 属性 取值为 1990。所有符合这 两条约定的元组 满足条件, 而其他的则不满足。· SELECT 子句说明了满足条件的元组的哪些属性将作为回答的组成部分。例中的星号( * ) 表明把整个元组作为回答的内容。查询的结果是由这个处理过程产生的所有元组组成的关系。解释 这种 查询 的一种 方法 是, 考虑 FROM 子句 中所 提及 的 关系 中的 每个 元组。 将WHERE 子句中 的条件应用 于这一 元组。更确切 地说, WHERE 子 句中所提 及的每个 属性都用这一元组中相应属性的值来代替。然后对条件进行求值, 如果为真, 则SELECT子句中出现的各属性将构成答案中的一个元组。比如当 SQL 查询处理程序遇到了一个 Movie 元组title year length inColor studioName producerC#Pretty Woman 1990 v119 true Disney 999 ( 这里, 999 是 虚构的该 电影 制片 人的证 书号 ) , 对于 WHERE 子句 的条 件, 用值’Disney'来代替属性 studioName, 而用值 1990 来代替属性 year , 由于这些值 是上述元组相应 的属性值, 因此,WHERE子句就变成了WHERE ’Disney' = ’Disney' AND 1990 = 1990这个 条件显 然为 真, 因此,Pretty Woman这一元 组将 通过WHERE子句 的检 测, 从而成为查询结果的一部分。 □5. 1. 1 SQL的投影如果愿意的话, 我们能够删除选定元组中的某些分量; 也就是说, 我们可以将SQL查询·371·
所产生的关系投影( projection) 到它自己的某些属性上。我们可以列出 FROM 子句中提到的关系的任何属性来代替SELECT子句中的星号( * ) 。结果将投影到所列的属性上。①例 5. 2 假 设 我 们 希 望 修 改 例 5. 1 中的 查 询, 只 产 生 电 影 的 名 称 和 长 度, 可 以 这样写:SELECT title,lengt hFROM MovieWHERE st udioName= ’Disney' AND year= 1990;结果是以title和length为表头的两列表。这个表中的元组都有两个属性, 即电影的 名称及其长度, 并且电影是由 Disney 在 1990 年制作的。例如, 关系模式 及其中的一个元 组就像这样:title lengthPretty Woman119 □ 有时, 我们希望生成的关系中列的标题与 FROM 子句所提到的关系中的属性能够有所区别。我们可以在属性名的后面加上关键字‘AS'和将在结果关系中出现的‘别名'。关键字 AS 是任选的, 有些较早的的 SQL 系统经常将其省略。这意味着, 别名可以直接跟在它所代表的属性的后面, 中间不需要任何标点符号。例 5. 3 我 们 可以 修改 例 5. 2 来 生 成一 个分 别由name和duration来 代 替tit le和lengt h 的关系, 如下所示:SELECT title AS name, length AS durationFROM MovieWHERE st udioName = ’Disney' AND year = 1990;结果是 和例 5. 2 相同的 元组集 合, 只是 由属性name和duration作 为列 的标题。 例如, 结果关系可能这样开始:name durationPretty Woman119 □ SELECT子句的另一个选项是用表达式来代替属性。例 5. 4 假如我们希望得到像例 5. 3 那样的输出, 但是电影的长度以小时为单位。我们可以将上例中的SELECT子句替换为SELECT title AS name,lengt h* 0. 016667AS lengthInHours于是将产生相同的 name-length 结果对, 但是电影的 长度将以小时计 算, 同时第 二列将以属性lengt hInHours为标题。 □例 5. 5 我 们 甚 至 能 够 允许 常 量 作 为SELECT子句的一项。这看起来好象像有意·471·①因 此,SQ L中 的关 键字SELE CT实 际上 密切 地对 应关 系代 数的 投 影运 算符 , 而 关 系代 数 中的 选 择运 算 符则对应于 SQL 查询的 W H ERE 子 句。
当然, 我们不指望读者 通过这 两个例子 就能成 为 SQL 编程 的专家, 但这 两个例子 已反映出SELECT-FROM-WHERE语句的“高级”性。一般而言, 这两个例子要数据库系统做的是:( 1) 检查FROM子句给出的Accounts关系的所有元组;( 2) 选出满足 WHERE 子句给出的某个判别条件的元组;( 3) 将这些元组按SELECT子句指定的某些属性组织成结果输出。实际上, 系统必须优化查 询, 并找 到一条 有效的途 径回答查 询的问 题, 即 使查询中 包含的关系非常复杂也是如此。 □IBM 很早就推出了关系模型以前的和关系的数据库管理系统( DBMS) 。另外, 为了实现和销售关系数据库管理系统, 一些新公司陆续成立。现在, 它们中的一部分已经迈入世界上最大的软件商之列。1. 1. 3 越来越 小的系统最初,DBMS是又大又昂贵的 的软件, 只能在大 型机上 运行。这种 规模是必 需的, 因为 存 储上 G 字节需 要 大 型的 计 算机 系 统。现 在, 一 个硬 盘 就能 容 纳上 G 字 节, 从 而 使DBMS在微机上的实现成为可能。甚至在很小的机器上都能运行基于关系模型的数据库系统, 并且, 就像 以前的电子表格 和字处理软 件那样, 数据库 系统也 在逐渐成 为各种计 算机应用的一个通用工具。1. 1. 4 越来越 大的系统另一方面, G 字节已经不够用了。公司的数据库常常有数百 G 的数据。此外, 随着存储器价格 的下降, 人 们也找到了存 储更多数 据的理 由。比如, 连锁零 售往往需 要T( 太, 1000G, 也就是 1012) 字节或 更多的信息来保存 很长一个时期以 来的每次销 售情况 ( 以便 计划库存量; 我们将在 1. 3. 4 节详细讨论) 。数据库系统不再局限于存储简单的数据—— 整数或短字符串, 它们还可以存储图象、声音、视频和其他类型的数据, 这些数据都需要相当大的空间。比如, 一小时的视频数据大约需要 1G字节的空间。预计到 2000 年, 存储从人造卫星传来的图象的数据库将需要几 P( 拍, 1 000T , 即 1015) 字节的存储量。处理如此巨大的数据库需要若干技术上的变革。例如, 中小型数据库现在存储在磁盘阵列上, 这些磁盘称为“第二存储设备”( 相对于第一存储器—— 主存而言) 。甚至, 有人会说数据库系统 和其他软件的最 主要区别就是 数据库系 统一般需 要处理 的数据量 太大, 内存中根本装不下, 而数据的存放通常都应以磁盘为主。下面的两种发展趋势能保证数据库系统更快地处理更大量的数据。第三存储器磁盘已经不能满足现在最大的数据库系统的需要了。因此, 人们研制出几种第三存储设备。第三存储设备或许可 以容纳上T字节, 但 要访问给定的单元 需要比磁盘多得 多的时间。典型的磁盘访问一个单元需要 10~20 毫秒, 而第三存储设备可能需要几秒钟的时间。第三存储设备包括将存放数据的对象传输给读出设备。这种移动由某种形式的机器·4·
大小写无关性SQL是 大小写 无关的, 也 就是说 它把 大写 字母和 小写 字母看 成相 同的 字母。例如, 尽管我 们选 择将 关键字 以大 写的形 式 写出, 如 FROM, 其实 将它 写成 F rom 或者from, 甚至FrOm都同样正确。同样, 属性、关系、别名等的名字也是大小写无关的。只有在引号之内, SQL 才区别大小写字母。因此, ’FROM' 和 ’from' 是不同的字符串;当然它们都不是关键字FROM。义, 但是应用程序有时会要求在SQL的输出显示中加入一些有用的信息。像如下的查询:SELECT title,length* 0. 016667AS length, ’hrs.' AS inHoursFROM MovieWHERE studioName= ’DISNEY' AND year= 1990;生成的元组如下:title length inHoursPretty Woman1 . 98334hrs. 我们安排了称为inHours的第三列, 这同第二列的标题length相对应。回答中的每个元组在第三列都有一个常量‘hrs.', 于是, 可把它看作是附加在第二列数值上的单位。 □5. 1. 2 SQL的选择通过 SQL 的 WHERE 子句, 我们 能够 获得关 系代 数中 的所有 选择 运算, 甚至 更多。跟在 WHERE 后面的表达式包括和普通的 C 或 PASCAL 语言一样的条件表达式。在构造表达式的时候, 我们可以使用 6 种通用的比较运算符对值进行比较: = 、< > 、< 、> 、< = 和> = 。这些运算符同它们在PASCAL中的用法完全一样, 有着明显的字面意义( 如果你不是一个 PASCAL 迷的话, 可以告诉你, < > 是“不等于”的意思) 。可以进行比较的值包括常量以及在 FROM 后面提到的关系的属性。我们也可以在对这些值进 行比较之前用+ 、* 等通用 算术运算符对这 些值进行数值计 算。例如, (year-1930) * (year- 1930) < 100 对于 1930 前后 9 年的年份(year) 都为真。我们可以对字符串用并置运算符©¦©¦进行连接操作; 例如, ’foo' ©¦©¦’bar' 得到值 ’foobar' 。 □一个比较的例子是例 5. 1 中的st udioName = ’Disney'检测关系 Movie 中的属性 studioName 是否等于常量 ’Disney' 。这个常量是字符串类型的; 在SQL中字符串通过在其前后加单引号来表示。在SQL中也允许有 数值常量、整数和实数, 并用通常的表示法来表示实数, 如- 12. 34 或 1. 23E 45。比 较的结果是布尔 值: 或者为 真( T RU E) 或者 为假( FALSE) 。布尔值可 以用逻辑 运算符组合起来,AND( 与) 、OR( 或) 、NOT( 非) 的用法和在PASCAL中的用法相同。例如,我们在例 5. 1 中可以看到两个条件如何用AND组合起来。当且仅当两个比较都满足的·571·
布尔值和位串的表示我们可以把SQL中的布尔值表示为位串的特殊情况。二进制位串由B加上引号内 0 和 1 的串来表示。因此, B ’011' 表示一个三位的串, 其中第一位是 0 而其余两位是 1。也 可以采用十六进制 表示法,X后面加 上引号内的十六 进制数字串( 0 到 9 和a到 f, 后者表示数字 10 到 15) 。例如, X ’7ff' 表示 12 位的串, 一个 0 后面有 11 个 1。注意: 每个十六进制数字表示 4 位二进制数, 并允许前几位为 0。布尔 值 T RUE 可以用 1 位二 进制 数表 示, 即, B ’1' 。类 似地, FALSE 由 B ’0'表示。时候, 例中 WHERE 子句的值才为真; 也就是说, 制片公司的名字为 ’Disney' 并且制作的年份是 1990, 结果才为真。下面是一些有复杂 WHERE 子句的查询例子。例 5. 6 下面的查询找出所有 1970 年以后制作的黑白电影(black-and-white) 。SELECT titleFROM MovieWHERE year> 1970AND NOT inColor;在这个条件中, 我们又遇到了两个布尔值的 AND。前者是普通的比较, 而后者是对属性值inColor取反。因为属性inColor是布尔类型, 因此对其进行布尔运算是有意义的。接下来, 考虑查询SELECT tit leFROM MovieWHERE ( year > 1970 OR lengt h < 90)AND studioName = ’MGM'此查询找出由米高梅制片公司制作的或者年份在 1970 年以后或者长度小于 90 分钟的电影的名称。注意在这里, 比较可以用圆括号来分组。这里之所以要用括号是因为 SQL中 的 逻 辑 运 算 符 同 其 他 语 言 中 所 用 到 的 一 样, 有 优 先 级:AND的 优 先 级 高 于OR, 而NOT的优先级则高于前两者。 □5. 1. 3 字符串 的比较如果两个字符串具 有相同的字符序 列, 那么它 们是相等的。SQL 允许说明不同 类型的字符串, 例如固定长度的字符数组( array) 和可变长度的字符列表( list) 。①这样, 我们就希望能够在不同的字符串类型之间进行合理的强制转换。比如说, 一个字符串如foo可能加上 7 个 填充( pad) 字符存储为固定 长度为 10 的字符 串, 也 可能存 储为可变 长度的字 符串。这样我们就希望这两种类型的值彼此相等, 并且也等于字符串常量 ’foo'。当我们 对两个字 符串用“大 于或小 于”这 一类运 算符( 如< 或> = ) 进行比较时, 我们·671·①至 少可以 认为 字符串 分别存 储在 数组或 列表 中, 至于它 们实 际上是 如何 存储的 则是 依赖 于实 现的 问题 , 而这些并没 有在 任何 SQ L 标准 中予 以规定 。
LIKE表达式中的换码字符如果想在LIKE表 达式的模式中包 含字符% 或- , 怎么办呢?SQL允许我们给单个模式指定换 码字符, 这 个字符由我们 任意选 定, 而 不是用某 个特定 字符作为 换码字符( 例如大多数 UNIX 命令中用反 斜杠作为换码字符) 。我们可以 在模式后面 加上关键字ESCAPE和带引号的选定的换码字符来实现这一点。模式中如果在% 或- 之前加上换 码字符, 就 会从 字面上 分别 解释为% 或- , 而不是 分别 代表任 意字 符序 列或 任意某个字符的通配符了, 例如, 表达式s LIKE ' x% %x%' ESCAPE ' x'中 x 作为模式 x% % x% 的换码字符。字符序列 x% 指的是单独的字符% 。于是, 这个模式就将匹配所有以‘%'开始并结束的字符串。是想知道这两个字符串按字典顺序( 即按词典顺序或按字母顺序) 是否其中一个先于另一个。也就是说, 如果有两个字符串 a1a2…an和 b1b2…bm, 如果 a1< b1, 或者 a1= b1而 a2< b2,或者 a1= b1, a2= b2而 a3< b3, …, 依 此 类 推, 那 么 前 者“ 小 于 ”后 者。 如 果 n < m 并 且a1 a2 …an = b1 b2 …bn , 那么 a1 a2 …an < b1 b2 …bm , 也就是说, 第一个字符串 是第二个字符串 的真前缀。例如, ’fodder' < ’foo' , 因为这两个字符串的前两个 字符 fo 均相同, 而 fodder的第三个字符先于 foo 的第三个字符; 同样, ’bar' < ’bargain' , 因为前者恰好是后 者的真前缀。在字符 串相等的场合, 我们或许希 望在不 同的字符 串类型 之间进行 合理的强 制转换。SQL还提供了基于简单模式(pattern) 匹配的字符串比较功能。下面是另一种形式的比较表达式s LIKE p其中,s是字符串, 而p是模式, 表示包含‘%'和‘-'两种任选的特殊字符的字符串。p中普通的字符只和 s 中完全 相同的字符匹配, 而‘% ' 却能同 s 中的 0 个或多个字符序 列匹配,‘-'能同s中的任意一个字符匹配。类 似地, 只有 当字符串s不匹配模 式p的时候,sNOT LIKE p才为真。例 5. 7 我们记得一部电影的名称是“Star x”, 并记得x是 4 个字母的单词。那么这部电影到底是什么呢? 我们可以通过查询找出所有这类电影名:SELECT titleFROM MovieWHERE title LIKE’St ar- - - -';该查询检测Movie的title属性是否是长度为 9 个字 符的字符串, 并且前五个字 符是Star和 一个空格。而后四个 字符可以是任意 的, 因 为任意四 个字符 的序列都 能同四个 连续的符 号‘- ' 相 匹 配。查 询 的 结 果 是 完 全 匹 配 的 电 影 名 的 集 合, 比 如“St ar Wars”和“St ar Trek”。 □例 5. 8 让我们来搜索电影名中含有所有格(' s) 的所有电影。想要的查询是这样的:·771·
SELECT titleFROM MovieWHERE title LIKE ' % ' ' s% ' ;要想理解这个 模式, 我 们就必 须首先考 察单引 号, 在SQL语句 中用单引 号把字符 串括起来, 这时单 引号并不表示它本 身。SQL 的习 惯用法是在字符串 中用两个连续的 单引号表示一个单独的单引号, 而不表示字符串的结束。这样, 模式中的”s将与电影名中的一个单引号和紧跟着的一个 s 相匹配。”s两边的% 字符将匹配两个任意的字符串。因此, 所有以' s作为子串的电影名都将匹配这个模式, 查询的结果将包括“Logan' s Run”或“Alice' s Restaurant ”之类的电影。 □5. 1. 4 日期和 时间的比 较SQL语言的不同实现版本通常都将日期和时间作为专用数据类型来支持。它们的值常常表示成多种格式, 如 5/ 14/ 1948 或 14 May 1948。在这里我们仅描述对格式有着非常明确规定的SQL2 标准表示法。日期( date) 由关键字 DAT E 及其后面带引号的特殊格式的字符串表示。例如, DAT E'1948-05-14'就符合这种格式。前面 4 个字符是表示年份的阿拉伯数字。接下来是连字符和表示月份的 2 位数字。值得注意的是, 就像我们的例子那样, 在 1 位数字表示的月份前面加上了一个 0。最后又是一个连字符以及表示日的 2 位数字。对于日, 就像月那样, 我们有必要为了构成两位的数字而在前面补 0。与此类 似, 时间(time) 由关键字T IME和带引 号的字符串来 表示。字符 串中小时 占两位数字, 按军用时钟计 时( 24 小时制 ) 。然后 依次是一个冒号、两位数字 的分, 再一 个冒号、两位数字的秒。如果需要秒的小数部分, 我们可以在后面加上一个小数点, 以及我们想要的多个有效位。例如, T IME ' 15∶ 00∶ 02. 5' 表示下午 3 点过两秒半, 这时所有的学生都下课了, 课在下午 3 点结束。我们可以使用对数字或字符串进行比较运算的比较运算符来对日期或时间进行比较运算。也就是说, 日期的小于( < ) 意味着第一个日期早于第二个日期; 时间的小于( < ) 意味着前者早于后者( 在同一天内) 。5. 1. 5 输出的 排序我们可能会要求查询生成的的元组按照一定的顺序表示出来。排序可以基于某个属性的值, 相等时根据第二个属性的值, 若还相等, 可以根据第三个属性的值, 依此类推。我们只要在select-from-where语句中加入一个子句就可以实现对输出的排序: ORDER BY〈属性表〉默认的顺序为升序, 但是我们可以通过附加关键字DESC( 指“降序”) 实现高序数先输出。同样, 我们可以用关键字 ASC 来指定升序, 但是实际上这个关键字是没有必要的。例 5. 9 下面是对例 5. 1 中我们原来查询的改写, 从关系Movie ( title, year, length, inColor , studioName, producerC# )中查询 1990 年Disney公司的电影。要使列出的电影先按长度排 序, 短的 在前, 对于 长度·871·
相同的电影, 再按字母排序, 我们可以这样描述:SELECT*FROM MovieWHERE st udioName=' Disney' AND year= 1990ORDER BY length, title;如果存在易于 理 解 的 属 性 顺 序 ( 由 于SQL中 的 关 系 是 由 属 性 表 说 明 的 ( 如 5. 7. 2节所述) , 所以应该有这种顺序) , 只要我们愿意, 就可以用属性号来代替属性名。因此, 上面的ORDER BY子 句 可 以 根 据 我 们 在 关 系Movie中 所 列 出 的 属 性 的 标 准 顺 序 而写成: ORDER BY3, 1; □5. 1. 6 本节练 习练习 5. 1. 1: 如果查询中的 SELECT 子句为SELECT A B我们如何才能知道其中的 A 与 B 是两个不同的属性, 或者 B 是 A 的别名?练习 5. 1. 2: 基于不断滚动的电影数据库的实例:Movie( title, year, length, inColor, studioName, producerC# )StarsIn(movieT itle,movieYear,st arName)MovieSt ar( name, address, gender, birthdat e)MovieExec(name,address,cert# ,netWorth)Studio( name, address, presC# )请写出下列SQL查询语句:* ( a) 找出米高梅制片公司( MGM studios) 的地址。(b) 找出桑德拉·布洛克(Sandra Bullock) 的出生日期(birt hdate) 。* ( c) B找出在 1980 年拍摄过电影的所有影星, 或者 拍摄过电影名中 含有“Love”的电影的所有影星。( d) 找出净资产至少 1 000 万美元的所有行政长官。(e) 找出所有的男影星或者是住址中含有Malibu的影星。练习 5. 1. 3: 用 SQL 写出下列的查询。查询将引用练习 4. 1. 1 中所描述的数据库模式:Product(maker,model,type)PC ( model, speed, ram, hd, cd, price)Laptop(model,speed,ram,hd,screen,price)Print er ( model, color, type, price)使用练习 4. 1. 1 的数据, 请写出查询的结果。* ( a) j找 出 价 格 低 于 1 600 美 元 的 所 有 个 人 计 算 机 ( PC) 的 型 号 ( model) 、速 度(speed) 以及硬盘容量(hd) 。* ( b) Q同 ( a) 的 要求, 另外 将“速度”( speed) 改为“兆赫 ”( megahert z) , 将“硬 盘”( hd)改为“吉字节”(gigabytes) 。·971·
( c) 找出打印机( Print er) 的制造商( maker) 。(d) S找出费用高于 2 000 美元的 便携式电 脑(laptop) 的型号、内存容 量(ram) 以 及屏幕尺寸( screen) 。(e) 从Printer关系中找出所有彩色打印机的元组。记住color是布尔值的属性。( f) 5找出具有 6 倍速或 8 倍速光驱( 6x or 8x cd) 而价格低于 2 000 美元的所有个人计算机的型号、速度以及硬盘容量。你可以把属性cd看作是字符串类型的。练习 5. 1. 4: 基于练习 4. 1. 3 的数据库模式:Classes(class,type,country,numGuns,bore,displacement)Ships ( name, class, launched)Battles(name,date)Outcome ( ship, battle, result )写出下列查询, 并根据练习 4. 1. 3 中的数据给出查询的结果:( a) C列出至少 拥有 十门 火 炮( numGuns) 的所 有舰 艇 等级 ( class) 的 名称 ( name) 和所属国家(country) 。( b) V找出 所有 在 1918 年以 前下 水 的舰 艇的 name, 而 结果 的名 称 用 shipName 来表示。( c) 找出所有在作战中沉没的舰艇的 name, 同时给出使它们沉没的 bat tle( 战役名) 。(d) 找出所有name和class同名的舰艇。( e) 找出 name 以字母 R 开头的所有舰艇的名称。! (f) 找出舰名中包含三个或三个以上单词( 如King George V) 的所有舰艇的名称。5. 2 涉及多个关系的查询关系代数的许多功能来自于它能够通过连接(joins) 、乘积(products) 、并(unions) 、交( intersections) 、差( differences) 等运算将两个或两个以上的关系连起来。我们在 SQL 中可以使用这五种运算之中的任何一个。集合论中 的运算 —— 并(union) 、交(intersection)和差( difference) 可以直接 出现在 SQL 中, 我们将在 5. 2. 5 节学习有 关的内 容。首先, 让我们来了解一下SQL的select-form-where语句是如何允许我们使用乘积(product) 和连接( join) 的。5. 2. 1 SQL中的乘积和连接SQL中有一个简单的方法用来把几个关系连到一个查询中: 在FROM子句中列出每个关系。这样, SELECT 和 WHERE 子句就可以引用 FROM 子句中列出的任何关系的属性了。例 5. 10 假设我们需要 得知《星球 大战》(Star Wars) 这部 电影 的制片 人(producer)的姓名。要想回答这个问题我们需要用到不断滚动的实例中的下列两个关系:Movie(tit le,year,length,inColor,st udioName,producerC# )MovieExec (name, address, cert# , netWorth)在关系Movie中给出了制片人的证书号(producerC# ) , 因此我 们可以通过对Movie·081·
进行简单查询得到这个号码。然后我们就可以通过对关系 MovieExec 进行的第二个查询找到拥有这个证书号的人的姓名。然而, 我们可以通过对一组关系 Movie 和 MovieExec 的一次查询来实现这两个步骤:SELECT nameFROM Movie, MovieExecWHERE title=' Star Wars' AND producerC# =cert# ;这个查 询要求我 们考虑 所有的元 组对 —— 其中 一个元 组来自关 系 Movie, 而 另一 个来自关系MovieExec。元组对的条件在WHERE子句中得到了说明:1. 来自 Movie 的元组的 t itle 属性值必须是“Star Wars”。2. Movie中元组的producerC# 属性值必须和MovieExec中元组的cert# 属性 值相同, 即具有相同的证书号。也就是说, 这两个元组一定是指向同一个制片人。每当 我们 找 到满 足 这两 个 条件 的 一对 元 组 后, 就从 来 自MovieExec的 元 组中 取 出name 属性作为结果的一部分。如果数据是我们所要求的, 那么只有当来自 Movie 元组的对应值是“St ar Wars”, 并且来自MovieExec元组的对应值是“George Lucas”( 乔治. 卢卡斯) , 两个条件才能同时满足。当且仅当这个时候, 电影的名称是正确的, 同时证书号也是符合的。这样,“George Lucas”将是查询所生成的唯一的值。 □5. 2. 2 消除属 性的二义 性有时我们的查 询涉及到几个关 系, 并且 在这些 关系当中 有两个 或两个以 上的属性 有着相同的名称。这样, 我们就需要找到一种方法, 使得能够指出这些拥有同样名称的属性到底指的是哪一个。SQL允许我们在属性的前面加上关系名和一个小圆点来解决这个问题。按照这种方法, R. A 指的是关系 R 的属性 A。例 5. 11 下面两个关系MovieStar ( name, address, gender, birt hdate)MovieExec(name,address,cert# ,netWorth)都有属性 name 和 address。假设我们要求找出所有具有相同地址的影星和行政长官的组合, 下面的查询将完成这项工作:SELECT MovieStar. name, MovieExec. nameFROM MovieStar,MovieExecWHERE MovieStar . address = MovieExec. address;在 这 个 查 询 中, 我 们 想 要 寻 找 一 对 元 组, 其 中 一 个 来 自MovieStar, 而 另 一 个 来 自MovieExec, 以便使它们的 address 分量相同。WHERE 子句的作用 就是要求这两个 元组中的address属性值相同。于是, 对于每个匹配的元组对, 我们从中取出 两个name属性,首先从 MovieStar 的元组中提取, 然后从另一个元组中提取。结果将会是这种姓名组合的集合, 比如:MovieStar. name MovieExec. nameJane Fonda T ed Turner□·181·
元组变量和关系名从技术上来说,SELECT和WHERE子句中对属性的引用总是针对某个元组变量的。然而, 如果一个关系只在 F ROM 子句中出现一次, 那么我们就可以用关系名作为它自己的元组变量。因此, 我们可以把FROM子句中的关系名R作为R AS R的简写。即使在没有二义性的场合, 我们也可以加上关系名和一个小圆点。例如, 我们可以很自然地将例 5. 10 中的查询写成这样:SELECT MovieExec. nameFROM Movie,MovieExecWHERE Movie. title = ' Star Wars' AND Movie.producerC# =MovieExec.cert#换句话说, 我们可以在这个查询中所用到的属性的任何子集之前加上关系名和圆点。5. 2. 3 元组变 量在查询涉及到 几个不同关系的 组合时, 通过在 属性前面 加上关 系作为前 缀来避免 属性的二义性是很成功的。然而, 有时我们需要做的查询涉及到同一个关系中的两个或多个元组。我们可以根据需要在FROM子句中多次列出关系R, 但是需要找到一种方法 来引用 R 的每一次出现。SQL 允许我们为 FROM 子句中关系 R 的每一次出现定义一个别名,用于作为引用的元组变量(tuple variable) 。FROM子句中每一次用到R都可以在后面加上( 任选的) 关键字 AS 和一个元组变量名。在 SELECT 和 WHERE 子 句中, 我们 可以 在属性 的前 面加 上适 当的 元 组变 量和 一个小圆点以避免属性的二义性。因此, 元组变量提供了关系R的另一个名字, 并在我们希望的时候把它放在关系 R 的位置上。例 5. 12 例 5. 11 要求查询具有同样地址 的影星和行政长官 的信息, 在这里, 我 们想要知道具有同样地址的两位影星的信息。查询从本质上来说是一样的, 但是现在我们必须考虑从关系 MovieStar 中选择两个元组, 而不是分别从 MovieStar 和 MovieExec 各选择一个元组。在两次用到MovieStar时, 我们用元组变量作为别名, 从而可以将查询写成这样:SELECT Star 1. name, Star2. nameFROM MovieStar AS Star1,MovieSt ar AS Star2WHERE St ar1. address = Star2. addressAND Star1. name < Star 2. name;我们看到在FROM子句中两 个元组变量St ar1 和St ar2 的 说明, 每个元 组变量都 是关系 MovieStar 的别名。元组变量在 SELECT 子句中用于引用两个元组的 name 分量。在WHERE子句中也 用这些别名来说 明它们所 代表的两 个MovieSt ar元组在address分 量上有相同的值。WHERE 子句中的 第二个条件, Star1. name < Star2. name, 指出第一名 影星的姓 名按照字母顺序应当在第二名影星的姓名之前。如果漏掉这个条件, 那么元组变量Star1 和·281·
Star2 就可能都指向同一个元组。我们当然会发现那些元组的地址是相同的, 于是就会生成同一影星 的一对对的姓名。①第 二个条件还能使 我们将每一对拥有 共同地址的影星 只按字母顺序输出一次。如果我们用< > ( 不等于) 作为比较运算符, 那么我们就会将已结婚的一对影星生成两次, 像这样:Star1 .name Star2 x.nameAlec Baldwin Kim BasingerKim Basinger Alec Baldwin□5. 2. 4 多关系 查询的解 释要定义我们已经用过的select-from-where表达式的含义可有几种方法。每个用于相同的 关系实 例的 查询按 每种 方法 都给出 相同 的答案, 在 这个 意义上, 这 几种 方法 是等 价的。我们将依次介绍它们。嵌套循环到目前为止, 我们在实例中隐含使用的语义是关于元组变量的。通过回顾, 我们可以知道关系名的别名是覆盖了相应关系的所有元组的元组变量。没有别名的关系名同样也是覆盖了该 关系本身的元组 变量。如果有几 个元组变 量, 我 们就可 以设想一 个嵌套的 循环, 其中每 个元 组变 量对应 一层 循环, 在每 层循 环中元 组变 量覆 盖了相 应关 系的 所有 元组。对元组变量所对应的元组的每一个赋值, 我们都要判断WHERE子句是否为真。如果为真, 我们 就生成一 个元组, 它包 含 SELECT 后面 各项目的 值( 注 意: 每项都会 按照元 组变量所对应的当前元组的赋值给出一个值) 。解答查询的算法在图 5. 2 中给出。 n假设FROM子句中的元组变量覆盖关系R1,R2, …,R n;FOR 关系 R 1 中每个元组 t1 DO FOR C关系R2 中每个元组t2DO…FOR ^关系Rn中每个元组tn DOIF 当用 t1, t2, …, tn 的值代替所有引用的属性时, WHERE子句得到满足T HEN 根据 t1, t2, …, tn 对 SELECT 子句中的属性进行求值并生成结果的元组值图 5. 2 对简单 SQL 查询的解答并行赋值在另一种等价的定义中, 我们不必显式地建立覆盖元组变量的嵌套循环。相反, 我们将考虑以任意的顺序或者并行的方式由适当关系的元组给相应的元组变量所有可能的赋·381·①如 果一个 人既 是影星 同时 又是行 政长 官, 那么同 样的 问题也 会出 现在例 5. 11 中 。通过要 求这 两个姓 名不 同,可以简 单地 解决这 个问题 。
Datalog解释和SQL解释读者应 该注意到 我们在 4. 2. 4 节 所描述 的解 释Datalog规 则的 第二 种方法 同我们给 SQL 的 select-from-where 语句的第二种解释是非常类似的。对于 Dat alog, 我们说过要考虑由适当关系的元组给规则体中的关系子目标所有可能的赋值。在SQL中,我们将要考虑元组给相应元组变量的所有可能的赋值。在这两种情况下, 算术子目标(SQL中WHERE子句的 一部分) 限制 了这些 元组的赋 值, 并 且结果 元组是通 过对规则头部( SQL 中的 SELECT 子句) 的计算得到的。值。对每一种这样的赋值, 我们考虑WHERE子句是否为真。每个使WHERE子句为真的赋值都为最后的结果提供一个元组; 该元组是由SELECT子句所 列出的属性按照 它的赋值构造出来的。转换到关系代数第三种方法是把SQL查询和关系代数联系起来。我们从FROM子句中的元组 变量开始并计算它们的笛卡尔积。如果两个元组变量指向同一关系, 那么该关系在乘积中会出现两次, 于是就把其属性改名以便所有的属性都有唯一的名字。同样, 不同关系中具有相同名字的属性也将改名以避免二义性。得到乘积以后, 我 们通过将 WHERE 子句以 明显的方式转换为 选择条件就可以 把选择运算符加到乘积上了。也就 是, 将 WHERE 子 句中每个对属性的 引用用笛卡尔积 中相应的属性来代替。最后, 我们由 SELECT 子句生成用于最后投影运算的属性表。投影运算的属性由选择运算来决定; 我们用笛卡尔积的相应属性 来代替 SELECT 中每个对属 性的引用。①例 5. 13 让我们把例 5. 12 的查询 转换为关系代数。首 先, 在 FROM 子句中有 两个元组变量, 都指向关系 MovieStar。因此, 表达式开始于MovieStar×MovieStar得到的关系有 8 个属性, 前 4 个和关系MovieStar的第一个副本中的属性name,address,gender和birthdate相 对应, 后 4 个 和关 系MovieStar的另 一 个副 本 中 相同 的 属性 相 对应。我 们可 以 通过 在这 些 属性 之 前加 上 元组 变 量 的别 名 和小 圆 点构 成 它们 的 名 称, 如Star1. gender, 但是为 简明起见, 我们引进新的 符号, 简单地 称这些属 性为 A1 , A2 , …, A8 。于是,A1 对应着Star1.name, 而A5 对应着St ar2.name, 等等。按照这种对属性命名的策略, 从 WHERE 子句中得到的选择条件就是 A2 = A6 和 A1< A5。投影的列是 A1, A5。这样,πA1, A5(σA2= A6AND A1< A5(ρM ( A1, A2, A3, A4) (MovieStar)× (ρN ( A5, A6, A7, A8) (MovieStar) ) )·481·①从 技术上 来说 , 关 系代数 并不 允许在SELE CT子句 中进 行算术 运算 , 而SQ L允 许( 如例 5. 4 所示 ) 。因此 , 对关系代 数的 投影运 算符进 行扩 展就显 得很 必要了 , 只 是由 于历史 的原 因, 投影是 以更 严格的 方式 定义的 。
( 京) 新登字 158 号内 容 简 介本书是由美国斯坦福大学两位著名的计算机学者 Jeffrey D. Ullman 和 Jennifer Widom 为初学数据库的人编写的基本教材。内容以对数据库的 使用为主, 讲述了数据建模, 关系数据模型,SQL语言 以及面向对象数据库的查询语言OQL的基本概念。作者根据当前数据库领域的发展, 对全书内容做了较大调整, 删除了大量旧内容, 增加了面向对象的新技术。本书内容简洁, 概念清楚, 适合作大 学本科生学习数据库的参考书。A Fir st Cour se in Database SystemsJeffrey D. Ullman, Jennifer WidomCopyright○C1997by Prentice Hall,Inc.Original English Language Edition Published by Prentice Hall, Inc.All Rights Reserved.本书中文简 体字版由Prent ice Hall出版公司授权清 华大学出版社独 家出版、发行。未 经出版者书面许可, 不得以任何方式复制或抄袭本书的任何部分。本书封面贴有清华大学出版社激光防伪标签, 无标签者不得销售。北京市版权局著作权合同登记号: 图字 01-98-006 号 图书在版编目(CIP) 数据 数据库系 统 基 础教 程/ ( 美) 额尔 曼, ( 美) 威多 姆 著; 史嘉 权 译. —北 京: 清 华 大学 出 版社, 1999. 8 ( 世界著名计算机教材精选) ISBN7-302-03646-2 Ⅰ. 数… Ⅱ. ①额… ②威… ③史… Ⅲ. 数据库系统-教材 Ⅳ.T P311. 13 中国版本图书馆CIP数据核字( 1999) 第 30397 号出版者: 清华大学出版社( 北京清华大学学研大厦, 邮编 100084)http: / /www.tup.tsinghua.edu.cn印刷者: 清华大学印刷厂发行者: 新华书店总店北京发行所开 本: 787× 1092 1/ 16 印张: 21. 75 字数: 501 千字版 次: 1999 年 9 月第 1 版 2001 年 11 月第 4 次印刷书 号:ISBN7-302-03646-2/T P·2029印 数: 16001~19000定 价: 36. 00 元
人传送动作来完成。例如, 光盘(CD) 就可能是第三存储设备的存储介质。一只装在 轨道上的机械手 伸向某张指定的 CD, 将其取出, 送到光驱, 而后将其装入光驱。并行计算能存储海量的数据固然重要, 但如果不能迅速访问, 也没有太大用处。因此, 大型数据库系统需要加速装置。一种重要的加速措施是采用索引结构, 我们将在 1. 2. 1 节和 5. 7. 7节介绍。另一种方法是利用并行操作, 在给定的时间内处理更多的数据。并行操作表现为多种形式。比如, 磁盘的读取速度相当低, 大约每秒几兆字节, 但如果我们并行地读许多磁盘, 处理速度 就会大 大提高( 即便 数据本来 是在第 三存储设 备上, 在 DBMS 访问 它们之 前也 会“高速缓存”在磁盘中) 。这些磁盘可能是并行计算机的组成部分, 也可能是分布式系统的一部分, 分布式系统包括 许多计算机, 每台机 器负责数 据库的一 部分, 必要时 它们通过 高速网络进行通信。当然, 和数据的海量存储一样, 数据的迅速传送也不能保证查询的迅速响应。我们还需要分解查询的算法, 以便充分利用并行计算机或分布式系统的所有资源。因此, 大型数据库的并行和分布式管理一直是研究和开发的热门话题。1. 2 数据库管理系统的结构这一节我们将简要介绍典型的数据库管理系统的结构。我们还要看看 DBMS 是如何处理用户的查询和其他数据库操作的。最后, 我们要考虑几个问题, 在设计既要容纳海量数据又要实现高速查询的数据库管理系统时将会碰到这些问题。但 DBMS 的实现技术不图 1. 1 DBMS的主要组成部分是本 书的主 题, 我们 关心的 是如 何有效 地设 计 和使 用数据库。1. 2. 1 DBMS的组成概述图 1. 1 给出 了数 据库 管理 系统 的主 要 组成 部分。最底部表示存储数据的地方。习惯上, 用圆盘形来表示存储数据的地方。注意, 我们在这里标注的 不仅有“数据”, 还有“元数 据”( met adata) —— 有关数 据结构的 信息。比如, 如果这个DBMS是关系的, 那么元数据就包括 关系 名、这些 关 系 的 属 性名, 以 及属 性 的 数 据 类 型( 如整型或长度为 20 的字符串) 。通常数据库管理系统需要维护数据的索引。索引是一种数据结构, 能帮助我们迅速找到数据项, 并给出其部分 值; 最常 见的索引的 例子是对 于特定关 系的某 一属性, 查找该 属性为给定值的所有元组。例如, 存放帐号和结余的关系也许有帐号的索引, 这样我们就能很快地查到某个给定帐号的结余。索引是数据的一部分, 而说明哪些属性有索引则是元数·5·
SQL语义非直观的后果假设R,S和T都是一元( 一个分量) 关系, 每个关系都只有一个属性A, 我们希望找到那些既在 R 中出现同时又在 S 或 T( 或 S 和 T 两者) 中出现的元素。也就是说, 我们希望计算R∩(S∪T) 。我们可以认为如下的SQL查询可以完成这项任务:SELECT R. AFROM R,S,TWHERE R. A = S. A OR R. A = T . A;然而, 考虑T为空的情况。由于R.A=T.A永远不能得到满足, 基于我们对OR运算的直观上的理解, 我们可能希望查询能准确地生成 R∩S 。但是, 无论用 5. 2. 4 节所述的三种等价定义中的哪一个, 我们都 会发现不管R和S中 有多少共同的元 素, 结果都为空。如果我们用图 5. 2 的嵌套循环的语义分析, 就会看到对元组变量 T 的循环只进行了 0 次, 这是因为该关系中没有 元组为元组变量 所覆盖。因此,FOR循环中的IF 语句永远不会 执行, 不 能生成任何结果。同 样, 如果 我们查看元组对 元组变量的赋值, 由于没有办法把元组赋指给T, 结果不存在赋值的情况。最后, 如果我们使用笛卡尔积的方法, 首先计算 R× S× T , 由于 T 为空, 所以笛卡尔积也为空。将以关系代数的形式再现全部的查询。 □5. 2. 5 查询的 并、交、差有时 我 们 希望 利 用关 系 代数 中 的集 合 运 算: 并、交 和差 将 若干 个 关系 综 合 在一 起。SQL提供用于查询结 果的相应运算符, 但要求 这些查询生成具有 相同属性集的关 系。这里使用关键字 UNION, INT ERSECT 和 EXCEPT 分别代表∪、∩和—。UNION 这 类关键字用于两个查询之间, 而查询必须用括号括起来。例 5. 14 假定我们想要身为电影行政长官并且净资产在 1 000 万美元以上的所有女影星的姓名和地址。用到下面两个关系:MovieStar(name,address,gender,birt hdate)MovieExec ( name, address, cert# , netWorth)我们可以写出如图 5. 3 所示的查询。 u1) (SELECT name,address2) FORM MovieStar3) WHERJE gender='F')4) INTERSECT5) (SELECT name,address6) FROM MovieExec7) WHERE netWorth> 10000000) ;图 5. 3 女影星和富有的行政长官的交集1) 到 3) 行生成了女影星的集合, 得到的关系其模式以name和address为属性。·581·
SQL查询的可读性通常, 人 们在 书写SQL查询 语句 的时 候总 是 将每 个重 要的 关键 字 如FROM或WHERE 另起一行。这种风格给读者提供了查询结构的直观线索。然而, 当查询或者子查询很短的时候, 我们有时也会像例 5. 15 那样, 把它写在单独的一行。这种风格, 既保持了完整查询的紧凑性, 也提供了很好的可读性。同样, 5) 到 7) 行生成了富有的行政长官的集合, 他们的净资产超过 1 000 万美元。这个查询也产生了一个关系, 其模式仅有 name 和 address 属性。由于二者的模式相同, 我们可以把它们相交, 并用 4) 行的运算符来实现。 □例 5. 15 依照 同 样的 思 路, 可 以对 分 别 从 两 个关 系 中 选 出 的两 个 人 的 集 合求 差。查询 '( SE LECT name, address FROM MovieStar)EXCEPT( SELECT name, address FROM MovieExec) ;给出了不同是电影行政长官的影星的姓名和地址, 而不考虑性别和净资产。 □在上面的两个例子中, 为方便起见, 用于相交或者求差的关系的属性都相同。但是, 如果需要得到通用的属性集, 我们可以像例 5. 3 那样把属性改名。例 5. 16 假 定我 们希望 得到 关系 Movie 或 St arsIn 中出现 的所 有电 影 的名 称和 年份, 而这两个关系是在不断滚动的实例中给出的:Movie ( title, year, length, inColor , studioName, producerC# )St arsIn(movieT it le,movieYear,starName)理想情况下, 这些电影的两个集合应该是相同的, 但实际上两个关系不一致也是很常见 的 事 情; 例如 可 能 有 些 电 影 并 没 有 列 出 影 星, 也 可 能 有 的StarsIn元 组 提 到 了 关 系Movie 中没有找到的电影。①因此, 我们可以这样写: (SEYLECE title,year FROM Movie)UNION(SELECT movieT itle AS t itle,movieYear AS year F ROM StarsIn) ;结果将是两个关系中提到的所有电影, 结果关系的属性为 tit le 和 year 。 □5. 2. 6 本节练 习练习 5. 2. 1: 使用我们不断滚动的电影实例的数据库模式 Movie( title, year, length, inColor, studioName, producerC# )StarsIn(movieT itle,movieYear,st arName)MovieSt ar( name, address, gender, birthdat e)MovieExec(name,address,cert# ,netWorth)·681·①可 以设法 避免 这种不 一致, 见 第 6 章 。
Studio( name, address, presC# )用SQL写出下列查询:* ( a) 电影“T erms of Endearment”中的男影星都有谁? (b) 哪些影星出现在米高梅公司(MGM) 于 1995 年制作的电影中? (c) 谁是米高梅制片公司的总裁?* ! ( d) 哪些电影比《乱世佳人》( Gong With the Wind) 更长? ! (e) 哪些行政长官比Merv Griffin更富有?练习 5. 2. 2: 基于练习 4. 1. 1 中的数据库模式Product(maker,model,type)PC ( model, speed, ram, hd, cd, price)Laptop(model,speed,ram,hd,screen,price)Print er(model,color,type,price)写出下列查询:* (a) c给出配置了容 量至少为 1G字节的硬 盘(hd) 的便携式 电脑(laptop) 的生产 厂商( maker) 及其速度( speed) 。* (b) c找出由生产厂商B生产的所有产品的型号(model) 和价格(price) 。( c) 找出所有出售便携式电脑( 而不出售 PC 机) 的生产厂商。! ( d) 找出在两种或两种以上 PC 机上出现的硬盘的容量。! (e) _找出拥有相同速度和内存的PC机的成对的型 号(model) 。每 对只列出一次;例如, 列出了( i, j ) 就不要列出( j, i) 。! ! (f) V找出所有 这样的 生产厂商, 其产 品中至 少有两种 不同类 型的计算 机(PC机 或便携式电脑) 速度最低为 133MHz。练习 5. 2. 3: 基于练习 4. 1. 3 中的数据库模式Classes(class,type,country,numGuns,bore,displacement)Ships ( name, class, launched)Battles(name,date)Outcome ( ship, battle, result )写出下列查询, 并用练习 4. 1. 3 中的数据对你写出的查询求值:( a) 找出排水量( displacement )大于 35 000 吨的舰艇。(b) 8列出参加瓜达尔卡纳尔岛(Guadalcanal, 简称瓜岛) 战役的舰艇 的名称、排 水量以及火炮的数量。( c) 1列出数据库中 所有提到的舰艇。( 记住: 所有提到 的舰艇 不一定都 出现在关 系Ships中) 。! ( d) 找出所有既拥有战列舰又拥有巡洋舰的国家。! (e) 找出在一次战役中受损, 而后又在另一次战役中投入战斗的那些舰艇。! ( f) 找出参战的舰艇中至少有三艘属于同一国家的所有战役。练习 5. 2. 4: 关系代数查询的通用格式为·781·
πL ( σC ( R1× R2× …× Rn )其中, L 是任意的属性表, 而 C 是任意的条件。关系表 R1 , R2 , …, Rn 可以包括重复多次的同一个关系, 在这种情况下可以假设为Ri 适当改名。说明如何表达SQL中这种格式的任意查询。! 练习 5. 2. 5: 关系代数查询的另一种通用格式为πL( σC( R1 R2… Rn)这里用了练习 5. 2. 4 中同样的假定; 唯一不同的就是用自然连接代替了乘积。说明如何表达SQL中这种格式的任意查询。5. 3 子 查 询在这一节, 我们将加深对WHERE子句中可能出现的表达式的理解。在此之前, 我们知道在条件中可以比较各种标量值( 诸如整数、实数、字符串或日期等简单值, 或者代表这些值的表达式) 。现在, 我们要扩展这种观点, 允许把整个元组甚至整个关系作为比较的内容。我们的第一步是学习如何在条件中使用子查询。子查询是对关系进行求值的表达式,例如select-from-where表达式就可以 是子查询。首先了 解如何产生关系 的值, 然后 将考虑SQL提供的某些运算符从而使我们能够在WHERE子句中比较元组和关系。5. 3. 1 产生标 量值的子 查询表达式 select-from-where 可以生成 其模式中有任意 数量属性的关系, 并且关系 中可以有任意数量的元组。然而, 我们往往只对单一的属性值感兴趣。此外, 有时我们可以从键码的信息推断出某个属性只生成单一的值。如果 这样, 我们 可以 利用由 括号 括起来 的select-from-where表达 式, 就像它 是一 个常量那样。尤其是, 它 可以出现在 WHERE 子句 中我们想要找到表 示元组分量的常 量或属性的任何地方。例如, 我们可以把这类子查询的结果同常量或属性相比较。例 5. 17 让我们回忆一下例 5. 10。在例 5. 10 中我们要查询《星球大战》( Star Wars)的制片人。必须对下面两个关系进行查询:Movie ( title, year, length, inColor , studioName, producerC# )MovieExec(name,address,cert# ,netWorth)因为只有前者有电影名称的信息, 而只有后者有制片人的姓名, 这些信息是由证书号联系起来的。证书号是制片人的唯一标识。我们拟定的查询是:SELECT nameFROM Movie, MovieExecWHERE title=' Star Wars' AND producerC# =cert#我们可以 换一种方法考虑 这个查询。我们仅仅 需要从关系 Movie 中 得到电 影《星 球大战》的制片人的证 书号。一旦有了证书 号, 我们 就可以对关系MovieExec进行查询, 从而找到拥有这个证书号的制片人的姓名。第一个问题, 得到证书号, 可以写成子查询; 而子查询的结果, 正如我们所 预期的那样, 是单一 的值, 可以用于 主查询 中达到和 上述查询 相·881·
同的效果。图 5. 4 中描述了这个查询。 1) SELECT name2) FROM MovieExec3) WHER E *cer t# =4) *( wSELECT producerC#5) FROM Movie6) WHERE title = 'Star Wars'7) ) ;图 5. 4 用嵌套子查询得到《星球大战》的制片人图 5. 4 中的 4) 到 6) 行是子查询。只看这个简单的查询本身, 我们留意到其结果是属性为 producerC# 的一元 关系, 并且, 我们希 望在这个关系中 只有一个元组。 这个元组 看起来 形如( 12345) , 也就是 说, 是 由某个整 数构 成的单 一分 量, 它可能 是 12345, 也 可能 是乔治·卢卡斯(George Lucas) 的别的证书号。如果 4) 至 6) 行的子查询生成 0 个或者多于一个元组, 就会出现运行时错误(run-time error) 。执行子查 询以后, 我 们可以执行图 5. 4 的 1) 至 3) 行, 好像 用值 12345 代 替了整个 子查询。也就是说,“主”查询将好像执行如下内容:SELECT nameFROM MovieExecWHERE cert# = 12345;查询的结果将是 George Lucas。5. 3. 2 涉及到 关系的条 件有许多 SQL 运算符可以用于关系 R 并且生成布尔值的结果。作为典型情况, 关系 R就是 select-from-where 子查询的结果。某些运 算符—— IN, ALL 和 ANY—— 还涉及 到标量值s, 在这种情况下, 要求关系R是只有一列的关系。这里给出这些运算符的定义:1. 当且仅当R非空时, 条件EXIST S R为真。2. 当且仅当 s 和 R 中的某一个值相等时, s IN R 为真。而且, 当且仅当 s 和 R 中的任一个值都不等时,s NOT IN R为真。这里, 我们假定R是一元关系。我们将在 5. 3. 3 节讨论当R相应的模 式有一 个以上的 属性并 且s是一个 元组时, 对IN和NOT IN运算符 的扩展。3. 当且仅当s比一元关系R中的每个值都大时,s>ALL R为真。与此类似,“> ”运算符可 以用其 他 5 个比 较运算符 中的任 何一个 来代 替而 具有类 似的 含义。例 如,s< >ALL R 和 s NOT IN R 是相同的。4. 当且仅当s比一元关系R中的至少一个值大时,s>ANY R为真。与此类似, 其他 5 个比较运算符中的任何一个都可以用来代替“> ”。例如,s=ANY R和s IN R是相同的。EXIST S,ALL和ANY运 算符都可 以通过 在整个 表达 式前面 加上NOT来取 反, 就像其他布尔值的表达式一样。于是, 当且仅当R为空时,NOT EXIST S R为真; 当且仅当·981·
s“不大于”( 译注: 原文误 为“不是”) R 中的最 大值时, NOT s > ALL R 为 真; 当 且仅当 s“不大于”( 译注: 原文误为“是”)R中的最小值时,NOT s>ANY R为真。我们将很快看到使用这些运算符的几个例子。5. 3. 3 涉及到 元组的条 件SQL 中的元组可以用括号里的标量值表来表示, 如( 123, ' foo' ) 或者( name, address,networth) 。前者由常量作为分量, 后者由属性作为分量。将常量和属性混合起来也是允许的。如果元组 t 和关系 R 具有 相同数量的分 量, 那 么在 5. 3. 2 节所 列出类型 的表达式 中对t和R进行比较就有意义。如t IN R或者是t< >ANY R。后一个比较指的是在R中存在不同于 t 的元组。注意在 将一个元 组和关系 R 中的 成员相比 较时, 我们必 须按照 R中假定的标准属性顺序对其分量进行比较。例 5. 18 图 5. 5 中是基于以下三个关系的SQL查询,Movie( title, year, length, inColor, studioName, producerC# )StarsIn(movieT itle,movieYear,st arName)MovieExec( name, address, cert# , netWorth)要求 寻找由 Harrison Ford( 哈 里森· 福特) 主演的 所有电影 的制片 人。它包含一 个主 查询, 嵌套于其中的第二级查询, 以及嵌套于第二级查询中的第三级查询。 1) SELECT name2) FROM MovieExec3) WHEREcert#IN4) ( ASELECT prodecerC#5)FROM Movie6) WHERE ( title, year ) IN7) ( SELECT movieTitle,movieYear)8) FROM StarsIn9)WHERE starName='Harrison Ford'10) )11) ) ;图 5. 5 查找 Harrison Ford 主演的电影的制片人我们应当从内到外地分析每个嵌套查询。因此, 让我们从最内层的嵌套子查询, 即 7)到 9) 行, 开 始。该 子 查 询 检 查 关 系 StarsIn 中 的 元 组, 并 从 中 找 出 starName 分 量 是' Harrison F ord'的所有元组。该子查询返回电影的名称和年份。回忆一下, 电影的键码是名称和年份而 不仅仅是名称, 因此我们需 要生成具 有两个属 性的元 组来唯一 地标识一 部电影。这样, 我们会希望 7) 到 9) 行生成的值看起来如图 5. 6 所示。现在, 让我们考虑中层子查询, 4) 到 6) 行。中层子查询搜索关系Movie以求找到所要的元组, 即元组中的名称和年份在图 5. 6 所假定的关系中。对找到的每个元组, 返回制片人 的证 书 号, 所 以 中 层 子 查 询 的 结 果 是Harrison Ford主 演 电 影 的 制 片 人 的 证 书 号 的集合。·091·
Title yearStar Wars 1997 ]Raiders of the Lost Ark1981 ]The Fugitive1993 ]… …图 5. 6 内层子查询返回的名称-年份对最后, 考虑从 1) 到 3) 行的“主”查 询。它搜 索 关系 MovieExec 来 寻找 符 合条 件的 元组, 这样的元组的cert# 分量是中层子查询所返回的集合中的证书号之一。每个元组都返回一个制片人 的姓名, 最 终给我们的是Harrison F ord主演电 影的制片 人的集合, 这正 是我们所需要的。 □顺便 说明一 下, 图 5. 5 中的 嵌套 查询和 许多 嵌套 查询 一样, 可 以 写成 单一 的select-from-where 表达式, 至于 在主查 询或 者子 查询中 提到 的每个 关系 都将 置于 FROM 子 句中。运算符IN将用WHERE子句中的等式来代替。例如, 图 5. 7 中的查询和图 5. 5 中的查询是 等价的。但对制片人 ( 如George Lucas) 重复出现的处 理方式存 在差异, 有关内 容将在 5. 4. 1 节讨论。 VSELECT nameFROM MovieExec, Movie, StarsInWHERE cert# = pr oducer C# ANDtitle = movieTitle ANDyear = movieYear ANDstarName='Harrison Ford';图 5. 7 不采用嵌套子查询的Ford的制片人5. 3. 4 相关子 查询最简单的子查询在整个过程中只求一次值, 并把结果用于较高一层的查询。嵌套子查询更复杂的应 用场合则要求子 查询多次求值, 每次 对子查询 中来自 子查询外 部元组变 量的某一项赋一个值。这类子查询称为相关子查询。让我们从一个例子开始。例 5. 19 我们要找出为两部或两部以上的电影所采用的电影名。我们从对关系Movie ( title, year, length, inColor , studioName, producerC# )中的所有元组进行外层查询开始。对于每个这样的元组, 我们在子查询中查找是否存在具有相同名称而年代更晚的电影。完整的查询如图 5. 8 所示。 V1) SELECT title2) FROM Movie AS Old3) WHERE year < ANY4) ( SELECT year5) FROM Movie6) WHERE title = Old. title7) ) ;图 5. 8 找出重复出现的电影名·191·
像分析其他嵌套查询那样, 让我们从 4) 至 6) 行最内层的子查询开始。如果将 6) 行的Old. t itle 替 换 为 常 量 字 符 串 比 如 ' King Kong' , 我 们 就 很 容 易 理 解 这 是 为 找 出 电 影“King Kong”制作的年份而进行的查询。当前的子查询稍有不同。唯一的问题是我们不知道Old.tit le的值到底是什么。然而, 当我们在 1) 至 3) 行的外查询中覆盖关系Movie的元组时, 每个 元组都提 供了Old.tit le的值。 然后我们 就利用Old.title的这 个值执 行 4) 至6) 行的查询来判断从 3) 行扩展到 6) 行的WHERE子句的真假。如果任 何同Old.tit le具有 相同名称 的电影的 年份比 元组变 量Old的 当前值 所对 应的元组中的电影年份晚一些, 那么, 3) 行中的条件就为真。除了Old元组中的年份是制作同名电影的最后年份, 其他年份该条件都为真。结果, 1) 到 3) 行生成的电影名比同名电影少一次。制作两次的电影将列出一次, 制作三次的电影将列出两次, 依此类推。①□写相关查询时, 知道名称的作用范围是相当重要的。一般而言, 如果某个元组变量对应的关系在其模式中具有某种属性, 那么子查询中的该属性就属于该子查询的 FROM 子句中的该元组变量。否则, 我们检查相邻的外层子查询, 然后检查再外层的子查询, 等等。于是, 图 5. 8 中 4) 行的 year 和 6) 行的 t itle 指的是这样的元组变量的属性, 该元组变量覆盖由 5) 行引入的关系 Movie 的副本( 也就是, 4) 到 6) 行的子查 询所访问的关系 Movie 的副本) 的所有元组。然而, 如果我们在属性前 面加上某个元 组变量 和小圆点 就可以 使属性属 于该元组 变量。这就是我们为外层查询中的Movie关系引入别名Old, 并在 6) 行引用Old.title的原因。注意: 如果在 2) 行和 5) 行的FROM子句中的两个关系不同, 我们就不需要用别名了。相反地, 在子查询中我们就可以直接引用在 2) 行提到的关系的属性。5. 3. 5 本节练 习练习 5. 3. 1: 基于练习 4. 1. 1 中的数据库模式Product(maker,model,type)PC ( model, speed, ram, hd, cd, price)Laptop(model,speed,ram,hd,screen,price)Print er ( model, color, type, price)写出下列查询。每个解答应该至少用一个子查询, 每个查询都用两种明显不同的方法( 如,用运算符EXISTS,IN,ALL和ANY的不同的集合) 。* (a) 找出速度至少为 160MHz的PC机的制造商(maker) 。(b) 找出价格最高的打印机。! ( c) 找出速度低于任何 PC 机的便携式电脑( laptop) 。! ( d) 找出具有最高价格的机器( PC 机、便携式电脑或打印机) 的型号( model) 。! ( e) 找出具有最低价格的彩色打印机的制造商。·291·①该 例是我 们必 须正视 关系代 数中 的关系 和SQ L中的 关系 之间 重 要区 别的 第一 个场 合。 所有 的SQ L关 系都可以有副 本; 也就 是说 , 它 们是包 , 而 不是 集合。副 本在SQ L关系 中出现 的方 式有若 干种 。我 们将 在 5. 4 节 详细讨 论这个问题 。
! ! ( f) 找出在具有最小内存( ram) 容量的所有 PC 机中具有最快处理器的 PC 机制造商。练习 5. 3. 2: 基于练习 4. 1. 3 中的数据库模式Classes ( class, type, country, numGuns, bore, displacement)Ships(name,class,launched)Battles ( name, dat e)Outcome(ship,batt le,result)写出下列查询。每个解答应该至少用一个子查询, 每个查询都用两种明显不同的方法( 如,用运算符EXISTS,IN,ALL和ANY的不同的集合) 。 ( a) 找出其舰艇拥有最大数量火炮的国家。* ! (b) 找出其中至少有一艘舰艇在战役中沉没的舰艇等级。 ( c) 找出具有 16 英寸口径( bore) 火炮的舰艇名称。 (d) 找出Kongo级舰艇参战的战役。! ! ( e) 在具有相同口径火炮的舰艇中找出火炮数量最多的舰艇的名称。! 练习 5. 3. 3: 不用任何子查询, 写出图 5. 8 的查询。! 练习 5. 3. 4: 考虑一下 关系代数 表达式 πL ( ( R1 R2 … Rn ) , 其中 L 是 属于 R1 的所有属性表。说明该表达式可以只用子查询用SQL写出。更确切地说, 写出等价的SQL表达式, 其中 FROM 子句的元组变量表中都只有一个元组变量。! 练习 5. 3. 5: 不使用交集或差集运算符写出下列查询:* ( a) 图 5. 3 的交集查询。(b) 例 5. 15 的差集查询。! ! 练习 5. 3. 6: 我们已经注意到 SQL 的某些运算符是冗余的, 某种 程度上它们总可 以用其他运算符来代替。例如, 我们看到s IN R可以用s=ANY R来代替。通过用不涉及到EXIST S( 不考虑 可能在 R 所代表 的表达式中出现) 的表达式 来代替 任何具有 EXIST S R和NOT EXIST S R形式的表达式来说明EXIST S和NOT EXIST S是 冗余的。提示: 尽管很少使用, 但是, 允许在 SELECT 子句中包含常量。5. 4 副 本到现在为止, 我们所研究的关系运算大部分是每次对一个元组的运算。例外的情况是在 5. 2. 5 节讨论的并、交和差运算符。在本节和下一节我们将研究一些作为整体作用于关系上的运算。在这里, 我们要面对这个事实: SQL 把关系当成包( bag) 而不是集合来使用,在一个关系中一个元组可以出现多次。在 5. 4. 1 节我们将看到怎样强制性地使运算的结果以集合的形式出现, 在 5. 4. 2 节我们将看到防止副本( duplicate) 的删除同样是可能的。5. 4. 1 副本的 删除正如我们在 5. 3. 4 节 提到的,SQL中关系的 概念不同于第 3 章 描述的关系的抽 象概念。关系作为一个集合, 任何给定元组的副本都不能超过一份。然而, 当 SQL 的查询生成一个新的关系时,SQL系统 通 常 并 不 删除 副 本。这样,SQL对于一个查询的应答可能把·391·
删除副本的代价可能有人试图在所有的SELECT后面都放上DIST INCT, 理论上这没什么 坏处。实际上, 从关系中删除副本的代价是昂贵的。通常, 必须把关系排序, 以便相同的元组一个接一个地出现。只有通过这样的方法把元组分成组, 我们才能决定是否应该删除给定的元组。为删除副本而对关系排序所用的时间通常要比查询本身所用的时间长。因此, 如果我们希望查询迅速运行, 那么就要谨慎地使用删除副本的操作。同一元组列出几次。回顾一下 5. 2. 4 节, 对SQL select-from-where查询含义的几种等价定义之一就是从FROM 子句中引用的关系的笛卡尔积开始的。每个这样的元组用 WHERE 子句中的条件检测, 通过检测的那些元组根据SELECT子句进行投影输出。该投 影可能导致由不 同的乘积元组产生的相同 元组, 如果 这样, 结 果元组的每个副 本将依次打印。此外, 由于 SQL关系具有副本并不存在什么问题, 所以笛卡尔积所形成的关系也可能具有副本, 并且每个同样的副本与来自其他关系的元组成对出现, 这就导致乘积中副本的激增。如果我 们不希望 产生副 本, 可 以在关键 字 SELECT 的后 面加上 关键字 DISTINCT 。这个关键字告诉SQL, 对于任何元组, 只生成一份副本。这样查询结果将保证没有重复。例 5. 20 让我们 再考虑 一下图 5. 7 中的查 询, 在那里, 我们要 求不使 用子查 询来 找出Harrison Ford( 哈里森·福特) 主演的电影的制片人。用图 5. 7 中的查询语 句,GeorgeLucas( 乔治·卢卡斯) 将在输出中出现多次。如果对于每个制片人我们只想看到一次, 可以把查询中的 1) 行修改成:1)SELECT DIST INCT name这样, 在打印前, 将删除列出的制片人中所有重复出现的名字。顺便说一下, 图 5. 5 中的查询, 由于使用了子查询, 因而不存在副本的问题。确实, 图5. 5 中 4) 行的子查询将多次产生乔治·卢卡斯的证书号。但是, 在 1) 行的“主”查询中, 我们对关系 MovieExec 中的每个元组都检查一次。假定在这个关系中只有一个关于乔治·卢卡斯的元组, 这样的话, 只有这个元组能够满足 3) 行中的WHERE子句。于是, 乔治·卢卡斯这个名字只打印一次。 □5. 4. 2 并、交、差中的副 本在 SELECT 语句中, 保留副本将作 为默认的情况, 只有在使用 DIST INCT 关键字 明确指出时才删除副本; 与此不同, 我们在 5. 2. 5 节介绍的并、交和差运算, 通常将自动地删除副本。为了防止删除副本, 我们必须在UNION、INT ERSECT或EXCEPT运算符的后面加上 关键字 ALL。这 样, 我 们将得 到这些运 算符的 包的语义, 就像 在 4. 6. 2 节 讨论 的那样。例 5. 21 再次考虑例 5. 16 中的UNION表达式, 但现在加上关键字ALL, 如下: ( SE YLECT title, year FROM Movie)UNION ALL·491·
索引是如何实现的读者 可能在数 据结构课 程中 学过, 哈希 ( Hash) 表是 建立索 引的 一种 很有效 的方法。的确, 哈希表在早期的DBMS中得到了广泛应用。而现在, 最常用的数据结构叫做平衡树( balanced-tree, 简记为 B-tree) 。平衡树是平衡二叉检索树的扩展。二叉树的每个节点最多有两个子节点, 而平衡树的每个节点可以有许多子节点。鉴于平衡树一般出现在磁盘上, 而不是内 存中, 人 们在设计时 就让平 衡树的每 个节点 占据一个 磁盘块( block) 。由于典型 系统中磁盘块的 大小为 212( 4096) 字节, 平 衡树的每个块可 能有几百个指向子节点的指针。所以, 平衡树的检索很少到三层以上。磁盘操作的真正开销在于访问的磁盘块的多少。因此, 作为典型的例子, 只检测三个磁盘块的平衡树检索比二叉树检索效率高得多, 因为后者往往需要访问不同磁盘块上的许多节点。平衡树和二叉树的这种区别从一个侧面说明了磁盘上的最佳数据结构与在内存中运行的算法的最佳数据结构是不同的。据的一部分。在图 1. 1 中, 我们还能看到存储管理程序, 它的任务是从数据存储器获得想要查询的信息, 并在接到上层的 更新请求时更新 相应的信息。DBMS 的另一 个组成部 分是查询 处理程序, 不过这个名字有点不太恰当。因为它不仅负责查询, 而且负责发出更新数据或元数据的请求。它的任务是接受一个操作请求后, 找到最佳的执行方式, 然后向存储管理程序发出命令, 使其执行。事务管理程序负责系统的完整性。它必须保证同时运行的若干个查询不互相冲突, 保证系统在出现系统故障时不丢失数据。事务管理程序要与查询处理程序互相配合, 因为它必须知道当前查询将要操作的数据( 以免出现冲突) , 为了避免冲突的发生, 也许需要延迟一些查询或操作。事务管理程序也与存储管理程序互相配合, 因为保护数据模式一般需要一个“日志”文件, 记录历次数据的更新。如果操作顺序正确的话, 日志文件将会记载更新的记录, 从而使系统出现故障时根本没有执行的操作在其后能重新执行。在图 1. 1 的最上方, 是三种类型的DBMS的输入:1. 查询。查询就是对数据的询问, 有两种不同的生成方式:(a) k通过通用的查询接口。比如, 关系数据库管理系统允许用户键入SQL查询语句, 然后将查询传给查询处理程序, 并给出回答。( b) r通过应用程序接口。典型的 DBMS 允许程 序员通过应用程 序调用 DBMS 来查询数据库。比 如, 使用 飞机订票系统的 代理可以通过运行 应用程序查询 数据库了解航 班的情况。可通过专 门的接口提出查 询要求, 接 口中也许包括 填城市名和时间之类的对话框。通过这种接口, 你并不能进行任意的查询, 但对于合适的查询, 这种方式通常比直接写SQL语句更容易。2. 更新。更新是指更新数据的操作。同查询一样, 更新操作也可以通过通用的接口或应用程序接口来提出。3. 模式更新。模式更新命令一般由被授予了一定权 限的人使用, 有时我们称这 些人·6·
( SELECT movieT itle AS title, movieYear AS year F ROM StarsIn) ;现在, 结果 中名 称和 年份 的出 现次 数 将等 于这 组数 据分 别 在关 系Movie和St arsIn中出现次数 的和。如果一部电影 在 Movie 关系中出现 一次, 并 且这部电影有 3 个影星 列入关 系StarsIn中( 这样, 这 部电 影出现 在StarsIn的 3 个 不同 的元 组中) , 那么这 部电 影的名称和年份将在并运算的结果中出现 4 次。 □同并运算一样, 运算符INT ERSECT ALL和EXCEP T ALL都是包的交和差。于是,如果 R 和 S 都是关系, 则表达式 R INT ERSECT ALL S 的结果是这样一个关系, 在该关系中, 元组t的出现次数是它在R中的出现次数和在S中的出现次数中的最小者。表达式 R EXCEPT ALL S 的结果 中, 元 组 t 的出现次 数是它在 R 中的 出现次数 减去它在S中出现次数的差, 规定这个差为正数。这些定义都是我们在 4. 6. 2 节关于包的讨论中出现过的。5. 4. 3 本节练 习练习 5. 4. 1: 用SQL写出练习 4. 1. 1 中的每个查询, 保证删除副本。练习 5. 4. 2: 用 SQL 写出练习 4. 1. 3 中的每个查询, 保证删除副本。! 练习 5. 4. 3: 对于练习 5. 3. 1 的每个答案, 判断你的查询结果中 是否存在副本。如 果存在, 则重写查询, 删除副本; 如果不存在, 则写一个不用子查询的查询, 要求有同样的、不存在副本的结果。! 练习 5. 4. 4: 对于练习 5. 3. 2 的答案, 重复练习 5. 4. 3 中的工作。5. 5 聚 合把一列中的值进行聚合( aggregation) 是另一类把关系看作整体的运算。所谓聚合指的是把一列中出现的一系列的值形成单一值的运算。比如说一列中各个值的和或平均值。在 SQL 中, 我们不 但可 以按列 进行 聚合, 而且 可以 根据 某个 条件, 比 如别 的 某一 列中 的值, 对关系中的元组进行分组, 然后按组进行聚合。5. 5. 1 聚合运 算符SQL 提供了 5 种适 用于关系中的列 的运算符, 这些运算符可以 产生该列的汇总 或聚合信息。这些运算符是:1. SUM, 该列中各个值的和;2.AVG, 该列中各个值的平均值;3. MIN, 该列中的最小值;4.MAX, 该列中的最大值;5. COUNT , 值的个数( 如果没有用 DIST INCT 明确删除副本, 则也包括副本) 。这些运算符用于标量表达式, 典型的是SELECT子句中的列的名称。例 5. 22 下面的查询找出所有电影的行政长官的平均净资产SELECT AVG(netWorth)·591·
FROM MovieExec;请注意, 根本没有WHERE子句, 所以把关键字WHERE省略是正确的。该查询检查关系MovieExec( name, address, cert# , netWorth)中的 netWorth 列, 对找到 的值求和, 每 个元组 ( 即使 该元组 是另 一个元 组的 副本) 取 一个值, 然后用元组的个数来除总和。如果没有重复的元组, 则查询给出我们期望的净资产的平均值。如果存在重复的元组, 那么, 一个元组出现n次的电影行政长官的净资产将在总和中统计 n 次。 □例 5. 23 下面的查询SELECT COUNT( * )FROM MovieExec;统计 MovieExec 关系中元组的个数。假定 name 是 MovieExec 的键码并且该关系中没有重复的元组, 则统计的元组数和统计的数据库中提到的电影行政长官的人数是相同的。上 面 的 星 号 ( * ) 表 示 整 个 元 组。把 COUNT 这 个 聚 合 运 算 符 用 于 整 个 元 组, 是COUNT特有的。把其他任何聚合运算符用于超过一列是没有意义的。如果我们希望确保不把重复的元组多次计数, 我们可以只 对name这 个属性计数, 并在它的前面使用关键字 DISTINCT , 像下面这样:SELECT COUNT ( DIST INCT name)FROM MovieExec;即使name不是键 码( 例如, 电影的同 一个行 政长官可 以有两个 不同的 元组, 或者两个 行政长官有相同的名字) , 上面的查询也只对每个名字统计一次。 □5. 5. 2 分组通常我们需要的并不仅仅是一列的平均值或其他某种聚合。相反地, 我们需要考虑关系中根据其他一列或多列的值划分成组的元组。例如, 假设我们想要计算每个制片公司生产的电影 的总长度( 以分钟计) , 那么我 们必须根据制片 公司对关系Movie中 的元组进 行分组, 并对每个分组中的lengt h列求和。我们还希望把结果作成表, 把制片公司和它们的总和联系起来, 表的格式如下:Studio SU M( length)Disney 12345 MGM54321 要生成 上面的表, 就要在 WHERE 子句的后 面跟随 一个 GROUP BY 子句。关键 字GROUP BY的后面将跟随一个分组属性表。在最简单的情况下,FROM子句只引用一个关系, 该关系按分组属性的值对其元组进行分组。所有 在SELECT子句中使用的聚 合运算符都只在分组内起作用。例 5. 24 在关系Movie(tit le,year,length,inColor,st udioName,producerC# )·691·
中找出各制片公司制作的所有电影的总长度的问题, 可用下面的查询来表达:SELECT studioName,SUM(length)FROM MovieGROUP BY studioName;我们可以想象 关系Movie中的元组 将重新组织并分 组, 于是 迪斯尼制片公司 的所有元 组放在一起, 米高梅公司的所有元组也放在一起, 等等, 就像图 5. 9 假设的那样。然后按组计算所有元组中长度分量的总和, 并且按组打印制片公司的名称和总长度。StudioNameDisneyDisneyDisneyMGMMGMooo图 5. 9 具有假设分组的关系观察一下例 5. 24 中SELECT子句是怎样包含两类术语的。1. 聚合: 聚合运算符用 于属性或者涉及 到属性的表达式。如 上所述, 按 这些术语 在每组的基础上进行计算。2. 属性: 比如 在这个实例中的 studioName, 同时也 出现在 GROUP BY 子句 中。在包含聚合运算 的SELECT子句中, 只有在GROUP BY子 句中出现的那些属 性才可以 在SELECT 子句中以非聚合的形式出现。虽然在涉及到 GROUP BY 的查询中, 分组属性 和聚合通常都 出现在 SELECT 子 句中, 但技术上两者并不都是必须的。例如, 我们可以写出这样的查询:SELECT studioNameFROM MovieGROUP BY studioName这个查 询将根据 制片公 司的名 称对 关系Movie中的 元组 进行 分组并 按组 打印制 片公 司的名称, 而不管和某个给定的制片公司名称相关的元组有多少。因此, 上面的查询和下面这个查询具有相同的效果:SELECT DIST INCT st udioNameFROM Movie在对多个关系进行的查询中同样可 以用GROUP BY子句。这 样的查询将按以 下步骤解释:1. 计算 FROM 和 WHERE 子句涉及到的关系 R。也就是, 关系 R 是 FROM 子句中提到的关系的笛卡尔积, 而WHERE子句中的选择运算则用于R。2. 根据 GROUP BY 子句中的属性对关系 R 中的元组进行分组。3. 把查询看作是对关系R进行的, 把生成的SELECT子句中的属性和聚合作为结果。·791·
SQL查询中子句的顺序到目前为止, 我们已经遇到了可以出现在SQL“select-from-where”查询中的所有6 种子句: SELECT , F ROM , WHERE, GROU P BY, HAVING 和 ORDER BY。只有前两个是必须的。其他任何附加的子句都必须按照上面列出的顺序出现。例 5. 25 假设我们想打印一张列出每个制片人制作的电影总长度的表。我们需要从如下两个关系中获取信息。Movie(title,year,length,inColor,studioName,producerC# )MovieExec( name, address, cert# , netWorth)以便首先通过使这两个关系中元组 的证书号(certificate number) 相 等对这两个关系 进行θ连接。通过这一步我们得到一个关系, 该关系中MovieExec的每个元组分别和Movie中该制片人的所有电影的元组组成对。然后我们根据制片人的姓名对该关系的元组进行分组。最后, 我们按组计算电影的总长度。这个查询见图 5. 10。 □1)SELECT name,SU M(length)2)FROM MovieExec,Movie3) WHERE producerC# = cert# 4) GROUP BY name;图 5. 10 计算每个制片人制作的电影的总长度5. 5. 3 HAVING子句假设我们并不想在例 5. 25 的表格中把所有的制片人都包括进去。我们可以在分组之前对元组加以限制, 使得不需要的组为空。比如说, 如果我们希望得到净资产超过一千万美元的制片人制作的电影的总长度, 我们可以把图 5. 10 中的 3) 行修改成:WHERE producerC# = Cert# AND netWorth ≥ 10000000然而, 有时我们希望在分组本身的某个聚合特性的基础上来选择分组。这样, 我们就可以在 GROUP BY 子句的后面加上 HAVING 子句。后者由关键字 HAVING 及随后的分组条件组成。例 5. 26 假设我们想要打印 1930 年以前至 少制作过一部电影 的制片人制作的 所有电影的总长度。我们可以在图 5. 10 后面加上子句:HAVING MIN(year) < 1930SELECT name, SUM( length)FROM MovieExec, MovieWHERE producerC# =cert# GROU P BY nameHAVING MIN( year) < 1930;图 5. 11 计算早期制片人制作的电影的总长度·891·
最后的查询, 如图 5. 11 所示, 将从分组的关系中去掉所有这样的分组, 即这些分组中每个元组的year分量都是 1930 或者更晚。 □5. 5. 4 本节练 习练习 5. 5. 1: 在 练习 4. 1. 1 的数 据库模式的基础 上, 写出下 列查询, 并 用练习 4. 1. 1 中 的数据计算你的查询结果。数据库模式为:Product(make,model,type)PC(model,speed,ram,hd,cd,price)Laptop (model, speed, ram, hd, screen, price)Print er(model,color,type,price)查询:* (a) 找出PC机的平均速度。 ( b) 找出价格超过 2 500 美元的便携式电脑( laptop) 的平均速度。 ( c) 找出厂商“A”生产的 PC 机的平均价格。 ! (d) 找出厂商“D”生产的PC机和便携式电脑的平均价格。 ( e) 找出各种不同速度的 PC 机的平均价格。* ! (f) 找出各厂商生产的便携式电脑的显示器平均尺寸。 ! (g) 找出至少生产三种不同型号的PC机的厂商。 ! ( h) 找出各厂商生产的 PC 机的最高价格。* ! (i) 找出速度超过 150MHz的各种速度的PC机的平均价格。! ! ( j) 找出所有生产打印机的厂商生产的 PC 机的硬盘平均容量。练习 5. 5. 2: 在 练习 4. 1. 3 中的 数据库模式的基 础上, 写出 下列查询, 并用练习 4. 1. 3 的数据计算你的查询结果。数据库模式为:Classes ( class, type, country, numGuns, bore, displacement)Ships(name,class,launched)Battles ( name, dat e)Outcomes(ship,battle,result)查询: ( a) 找出战列舰( batt leship) 的等级数。 (b) 找出各等级战列舰火炮的平均数。! ( c) X找出战列舰火炮的平均数。请注意( b) 和( c) 之间的差别; 我们是否根据某一等级舰艇的数量而对该等级加权?! ( d) 找出各等级舰艇中的第一艘下水的年份。! ( e) 对于每个等级都找出在战役中沉没的舰艇数。! ! (f) 对于至少有三艘舰艇的各个等级找出在战役中沉没的舰艇数。! ! ( g) d舰炮发射的炮弹的 重 量( 以磅 为 单位) 约 等 于口 径( 以 英寸 为单 位) 的 立方 的1/ 2。找出每个国家舰艇上所用的炮弹的平均重量。·991·
5. 6 数 据 库 更 新到 现 在 为 止, 我 们 一 直 把 重 点 放 在 标 准 的SQL查 询 格 式 上, 这 就 是:select-from-where 语句。SQL 中还有一些其他的并不返回结果但会改变数据库状态的语句格式。在这一节, 我们将把重点放在三类语句上, 它们可以使我们做到:1. 在关系中插入元组。2. 从关系中删除某些元组。3. 修改某些已有元组中某些分量的值。我们把这三类操作统称为数据库更新。5. 6. 1 插入插入语句的基本格式包括:1. 关键字INSERT INT O;2. 关系名 R;3. 括在括号中的关系R的属性表;4. 关键字 VALUES; 以及5. 一个 元组表达 式, 也 就是括 在括号中 的一组 具体的值, 每个 值都对 应属性 表 3 中的一个属性。因此, 插入的基本格式为: INSERT INT O R( A1 , …, An ) VALUES ( v1 , …, vn ) ;通过把值vi 赋给对应的属性Ai(i= 1, 2, …,n) , 可以生成一 个元组。如果属性 表并不包括 关系 R 中 的所有 属性, 那么在生 成的元 组中没 有包 括进来 的属 性将 采用默 认值。关于默认值我们将在 5. 7. 5 节讨论。最常见的默认值是空(NU LL) , 我们已经在 4. 7. 4 节进行了初步的讨论, 在 5. 9 节我们将在 SQL 的范围内对它作进一步的讨论。我们可 以暂时把空(NU LL) 看作是, 当不知道分量的确切值时, 作为占位符使用的一个值。例 5. 27 假 如我 们想把 Sydney Greenstreet 加 入到 电影 T he Maltese Falcon 的 影星名单中, 我们可以用:1) INSERT INT O StarsIn( movieT itle, movieYear , starName)2)VALUES(' The Maltese Falcon', 1942,' Sydney Greenstreet') ;执行这个语句的效果就是, 2) 行上具有三个分量的元组将插入到关系 StarsIn 中。由于St arsIn的所有属性都在 1) 行列出来了, 所以就不需要再加默认的分量了。2) 行的值按照给定的顺 序与 1) 行的属性相匹 配, 于是 ' T he Maltese Falcon' 成为属 性 movieT itle 对应的分量的值, 依此类推。 □如果像例 5. 27 那样为关系中所有的属性都提供了值, 那么我们可以省略关系名后面的属性表。这就是说, 也可以写成:INSERT INT O StarsInVALUES(' T he Maltese Falcon', 1942,' Sydney Greenst reet') ;·002·
插入的时间图 5. 12 说明了SQL语句语义上的微妙之处。原则上, 计算 2) 到 6) 行的查询应该在执行 1) 行 的插入操作之前 就完成。这样, 就不可能发生 在 1) 行加入到 Studio 中的新元组影响到 4) 行中的条件。然而, 为了提高 效率, 有 的实现可能这样 来执行这个语句, 从而在执行 2) 到 6) 行的过程中, 只要一找到新的制片公司, 就立刻改变 Studio。在这个特例中, 插入操作是否延迟到查询完全计算完无关紧要。但是, 有些其他查询, 如果改变插入的时间, 查询的结果就可能发生变化。例如, 设想我们去掉图 5. 12 中2) 行的DIST INCT。如果我们在执行任何插入操作之前, 先计算 2) 到 6) 行的查询, 那么新的 制片公 司名 在 Movie 的元组 中出 现几次 就会 在查 询的结 果中 出现几 次, 从而也就在关系Studio中 插入几次。可是, 如果我们在 计算 2) 到 6) 行的查 询的过程中只要一找 到新的制 片公司就 将之立 刻插入 到关 系 Studio 中, 那么 就不 会把 相同的 新制片公司插入两次了。相反地, 一旦把新的制 片公司插入一次, 它的名称 就不再满足 4)到 6) 行的条件了, 从而也不会在 2) 到 6) 行的查询结果中第二次出现了。然而, 如果选择这种写法, 就必须保证值的顺序与关系中属性的标准顺序相同。在 5. 7 节我 们 将 看 到 如 何 说 明 关 系 模 式, 并 将 看 到 在 说 明 时 会 给 属 性 提 供 一 个 顺 序。如 果 在INSERT语 句中省略了属性 表, 那么值 就按照这 个假定 的顺序和 属性进行 匹配。如果 你不能确定属性的标准顺序, 那么最好是把它们按照你喜欢的顺序列出来。上面描述的简单的INSERT仅仅把一个元组加入到关系中。我们可以用子查询计算出要插入的元组集, 用来代替为每个元组赋以显式的值。该子查询代替上面描述的关键字VALUES和INSERT语句格式中的元组表达式。例 5. 28 假如我们想要在关系Studio(name,address,presC# )中插入所有在关系Movie(tit le,year,length,inColor,st udioName,producerC# )中提到, 但并没有 在 Studio 中 出现的电影制片 公司。由于无法确定 这样一个制片公 司的地 址 和 总 裁, 因 此 我 们 只 能 满 足 于 用NULL值 和 要 插 入 的St udio元 组 的address和presC# 属性相对应。图 5. 12 中给出了实现该插入的一种方法。 V1) INSE RT INTO Studio( name)2)SELECT DISTINCT studioName3)FROM Movie4) WHERE ^studioName NOT IN5) ( SELECT name6)FROM Studio) ;图 5. 12 加入新的制片公司和大多数嵌套SQL语 句相似, 从 内向外检查图 5. 12 是最方 便的。5) 行和 6) 行 生成关系Studio中所有的制片公司名。然后, 4) 行以来自关系Movie的制片公司名不属于上·102·
插入、删除和副本关于 插入和 删除 与SQL关系中 所允 许的 副本 的 相互 影响 呈现 出一 些 棘手 的情况。首先, 插入操作将指定的元组加入到关系中, 而不管插入之前关系中是否存在该元组。这样, 如果已经存在一个与T he Maltese Falcon和Sydney Greenstreet相对应的StarsIn 元组, 那么, 例 5. 27 的插入操作将增加同一个元组的另一个副本。如果我们在插入之 后执行例 5. 29 的删除 操作, 由 于两个 元组 都满足WHERE子 句的条 件, 因此就把两个元 组都删掉了。我们得 到一个令人吃惊 的结论, 对关 系 StarsIn 的插 入操作和随后的删除操作得到的结果和这两个操作之前的关系不同。另外就是删除语句, 似乎在说要删掉单个元组, 而实际上删掉了不只一个元组。事实上, 在 SQL 中无论如何也没有办法从两个完全相同的元组中删掉一个。述制片公司为条件进行检查。现在, 我们观察 2) 到 6) 行生成的, 在关系Movie中 而不在关系St udio中的制片 公司名的集合。在 2) 行用 DIST INCT 保证了在这个集合中每个制片公司只出现一次, 而不管它同多少部电影相关联。最后, 1) 行把每个这样的制片公司插入到关系St udio中, 而对其中的属性 address 和 presC# 则赋以 NULL 值。 □5. 6. 2 删除删除语句包括:1. 关键字DELET E FROM,2. 关系名, 比如说 R,3. 关键字 WHERE, 以及4. 一个条件。即删除的格式为DELETE F ROM关系名WHERE〈条件〉;该语句执行的结果就是从给定的关系中删除满足条件 4 的所有元组。例 5. 29 我们可以从关系St arsIn ( movieT itle, movieYear, starName )中删除Sydney Greenstreet主演电影T he Maltese Falcon这样一个事实, 通过SQL语句来实现:DELET E FROM StarsInWHERE movieT itle = ' T he Maltese Falcon ' ANDMovieYear = 1942 ANDStarName=' Sdney Greenst reet';注意: 和例 5. 27 中的插入语句不同, 我们不能简单地指定要删除的元组。相反地, 我们必须通过WHERE子句准确地描述出该元组。 □例 5. 30 这里有另一个删除的实例。这次, 我们用不只一个元组能满足的条件从关系MovieExec(name,address,cert# ,netWorth)·202·
中一次删除多个元组。语句DELET E FROM MovieExecWHERE netWorth < 10000000;将删除所有净资产较低—— 不到 1 000 万美元的电影的行政长官。 □5. 6. 3 修改尽管我们会认为对元组的插入和删除都是对数据库的“修改”, 但 SQL 中的修改 操作却是对数据库 的一种非常具体 的更改: 数 据库中已 经存在的 一个或 多个元组 的某些分 量有所改变。修改语句的一般格式为:1. 关键字UPDATE,2. 关系名, 比如为 R,3. 关键字SET,4. 一系列公式, 每个公式都将关系R的某个属性设置成表达式或常量的值,5. 关键字 WHERE, 以及6. 条件。即, 修改的格式为UPDATE R SET〈新的赋值〉WHERE〈条件〉;每个新的赋值( 上面第 4 项) 都是由属性、等号和公式组成。如果有多个赋值, 中间用逗号分开。该语句的作用首先 是在关系 R 中找 到所有满足条件 6 的元组; 然后通过对 4 中 的公式进行 求值, 以及对关 系R中 相应属性 所对应 的元组 分量 进行赋 值, 从而 使每个 满足 条件的元组有所改变。例 5. 31 让我们对关系MovieExec( name, address, cert# , netWorth)进行修改, 在所有的身为制片公司总裁的电影行政长官 前加上头衔“Pres. ”。所要求 的元组应满足的条件是, 其证书号出现在关系 Studio 中某个元组的 presC# 分量中。我们这样描述该修改语句:1) UPDAT E MovieExec2)SET name=' Pres.'©¦©¦name3) WHERE cert # IN ( SELECT presC# FROM Studio ) ;3) 行检测MovieExec中元组的证书号是否是关系Studio中总裁的证书号之一。2) 行执行对选定的元组的修改操作。回顾一下运算符©¦©¦, 它表示字符串的拼接, 结果根据 2) 行等号右 边的表达式将字 符Pres. 和一 个空格放到该元 组name分量 的原有值 之前。新的字符串 成为该元组name分量的 值; 结果头衔' P res.'就 附加在name的原有 值之前。 □5. 6. 4 本节练 习练习 5. 6. 1: 基于练习 4. 1. 1 中的数据库模式·302·
Product ( make, model, type)PC(model,speed,ram,hd,cd,price)Laptop (model, speed, ram, hd, screen, price)Print er(model,color,type,price)写出下列数据库更新操作, 描述对练习 4. 1. 1 中的数据进行更新的效果。(a) 0用两个INSERT语句将下 述事实存入数据 库: 生产 厂商C制造的 型号为 1100的 PC 机, 速度 240M , 内存 32M, 硬盘 2. 5G, 12 倍速光驱, 售价 2 499 美元。! (b) 3插入这些事实: 对于每一种PC机, 都有一种具 有相同速度、内存和硬盘的 便携式电脑, 而屏幕为 11 英寸, 型号大于 1100, 价格超过 500 美元。(c) 删掉所有硬盘容量不到 2G字节的PC机。( d) 删掉由不制造打印机的厂商所制造的所有便携式电脑。(e) 厂商A收购了厂商B, 将所有由B制造的产品改为由A制造。( f) #对每种 PC 机, 将内存的容量加倍, 硬盘的容量增加 1G 字节。( 记住: 一些 属性可以通过UP DATE语句进行更改。)! ( g) 1对由生产厂商 E 制 造的所有便携式 电脑, 将屏 幕尺寸增加 1 英 寸, 并且将 价格降低 100 美元。练习 5. 6. 2: 基于练习 4. 1. 3 中的数据库模式Classes(class,type,country,numGuns,bore,displacement)Ships ( name, class, launched)Battles(name,date)Outcomes ( ship, battle, result)写出下列数据库更新操作。描述对练习 4. 1. 3 中数据进行更新操作的效果。* ( a) J两艘 Nelson 级的英国战列 舰—— Nelson 号和 Rodney 号 —— 均于 1927 年 下水, 拥有 9 门 16 英寸的火炮以及 34 000 吨的排水量。将这些事实插入到数据库中。(b) e三 艘 意 大 利Vittorio Veneto级 战 列 舰 中 的 两 艘 ——Vittorio Veneto号 和Italia 号 —— 均于 1940 年下水; 该等级的 第三艘战列舰, Roma 号, 于 1942 年下水。它们都有 9 门 15 英寸的火炮以及 41 000 吨的排水量。将这些事实插入到数据库中。* (c) 从关系Ships中删除所有在战役中沉没的舰艇。* ( d) L修改关系 Classes, 从而 用厘米来计量火 炮口径( 1 英寸= 2. 5 厘米) , 用公 吨来计量排水量( 1 公吨= 1. 1 吨) 。( e) 删掉所有舰艇数量少于 3 艘的等级。5. 7 用SQL定义关系模式在 本节 我们开 始讨 论数据 定义 ( data definition) , 即 SQL 中涉 及到 描述 数据 库中 信息结构的部分。相反, 以前讨论的SQL的诸方面 —— 查询和更新 —— 通常称为数据 操作·402·
为数据库管理员, 他们能够更改数据库模式或者建立新的数据库。比如, 假设查询和报告系 统(Inquiry and Reporting System,IRS) 要 求银 行报 告顾 客的 社会 保险 号 和他 们的 利息, 那 么银 行 就需 要 在存 放 顾 客信 息 的关 系 中 加 入 一个 新 的 属 性—— socialSecurityNo( 社会保险号) 。1. 2. 2 存储管 理程序在简单的数据库系统中, 存储管理程序也许就是底层操作系统的文件系统。但为了提高效率,DBMS往往直接控制磁盘存储器, 至少在某些情况下是这样。存储管理程序包括两个部分—— 缓冲区管理程序和文件管理程序。1. 文 件管理 程序 对文 件在 磁 盘上 的位 置保 持跟 踪, 并 且负 责取 出一 个 或几 个数 据块, 而数据块中含有缓冲区管理程序所要求的文件。磁盘通常划分成一个个连续存储的数据块, 每个数据块能容纳许多字节, 从 212到 21 4( 大约 4 000 到 16 000) 字节之间。2. 缓冲区管理程序控制处理主存。它通过文件管理 程序从磁盘取得 数据块, 并 选择主存的一个页面存放其中的一块。缓冲区管理程序会把数据块在主存中保留一段时间, 但当另一个数据块需要使用该页面时, 就把原数据块写回磁盘。当然, 如果事务管理程序发出请求, 缓冲区管理程序也会把数据块写回磁盘( 参见 1. 2. 4 节) 。1. 2. 3 查询处 理程序查询处理程 序的任务是, 把很高 级的语 言表示的 查询或 数据库操 作( 如 SQL 查询 语句) 转换成对存储器 数据—— 比如 某个关系 的特定 元组或部 分索引 —— 的 请求序 列。通常, 查 询处理任务最困难 的部分是查询优 化, 也就是说 选择好的 查询计 划, 即 对存储器 系统选择好的请求序列以回答所要求的查询。例 1. 2 假设某银行有一个包含两种关系的数据库:1.Customers( 顾客) 是一张表, 给出每个顾客的姓名、社会保险号和地址。2. Accounts( 帐户) 是 一张表, 给出每 个帐户 的帐号、结 余和 户主的 社会 保险号。 注意, 每个帐户都有一个主户主, 其社会保险号将用于税款报告; 当然, 一个帐户也可能还有其他的户主, 但这两种关系里不包含此类信息。假设查询是“查找以Sally Jones为主户主的所有帐户的结余”。查询处理程序必须找到在这两种关系上实施的查询方案, 从而得到查询结果。回答查询所需的步骤越少, 查询方案就越好。通常开销大的步骤是存储管理程序把磁盘数据块复制到缓冲池的内存页面或由内存页面写回磁盘。因此, 在评价查询方案的代价时只考虑这些磁盘操作是合理的。为了 回 答这 项 查询, 我们 需 要从Customers关 系 中找 到Sally Jones的 社 会保 险 号( 这里, 我 们 假 设 只 有 一 个 顾 客 叫 Sally Jones, 虽 然 事 实 上 可 能 不 止 一 个) 。然 后 再 从Accounts关系查找具有该社会保险号的每个帐户, 并打印这些帐户的结余。一种简单但代 价很高的方案 是检查 Customers 关 系的所有 元组, 直到我 们在顾客 姓名域中找到Sally Jones。平均来讲, 我们需要查找半数的元组才能找到目标。鉴于银行有许多的顾客, Customers 关系会占据许 多的磁盘块, 这种做法代价 很高。即便我们找 到了Sally Jones的社会保险号, 还远没有结束。我们还得查找帐户关系中的元组, 以找到指定·7·
( data manipulat ion) 。本节 的主 题是 关系模 式的 说明。我 们将 看到 如何 描 述新 的关 系, 即SQL中 所谓 的“表”( table) 。我们能够描述的方面包括属性名, 属性的数据类型, 以及诸如“键码”之类的某些有限定的约束类型。5. 8 节涉及到“视图”(view) , 视图是虚拟关系而不是数据库中实际存储的真正关系, 至于一些关于关系约束的更复杂的问题则留到第 6 章。5. 7. 1 数据类 型首先, 我们介绍SQL系统支持的主要的数据类型。所有的属性都必须具有数据类型。1. 固定或可变长度的字符串。类型 CHAR( n) 表示固定长度为 n 个字符的字符串。这意味着, 如 果属性类型为CHAR(n) , 则任何元组中 对应于该属性的分 量都是n个字 符的串。VARCHAR( n) 表示顶多为 n 个字符的串。该类属性所对应的分量可以是 0 到 n 个字符的串。SQL允许字符串类型的值之间进行合理的强制转换。如果字符串成为更长的固定长度字符串类型的分量值, 通常就用尾随的空格来填充。例如, 字符串 ' foo' , 如果成为类型为CHAR( 5) 的属性所对应的分量值, 则采用的值为' foo'( 在第 2 个o后面有两个空格) 。如果该分量的值同另一个字符串相比较( 见例 5. 1. 3) , 则填充的空格就会忽略不计。2. 固定或可变长度的位串。同固定和可变长度字符 串相似, 但 是其值为位串而 不是字符串。类型 BIT ( n) 表示长度为 n 的位串, 而 BIT VARYING( n) 则表示长度顶多为 n 的位串。3. 类型 INT 或 INT EGER( 它们是同义词) 表示典型的整型值。类型 SHORT INT 也表示整型, 但是根据具体的实现, 允许的位数 可能会少一些( 这同C中的类型int和shortint 相似) 。4. 浮点数可以通过多种方式来表示。我们可以用类 型FLOAT或REAL( 它们 是同义词) 来表示典型的浮点数。可以通过类型 DOUBLE PRECISION 得到高精度浮点数; 这些 类 型 之 间 的 区 别 也 同C中 的 情 形 相 似。SQL还 有 固 定 小 数 点 的 实 数 类 型, 如,DECIMAL( n, d) 允许包含 n 个十进制数字, 小数点位于从右数第 d 位。例如, 0123. 45 就是类型DECIMAL( 6, 2) 的一个可能值。5. 日期和时间可以通过数据类型 DAT E 和 T IME 来表示。请 回忆我们在 5. 1. 4 节对日期和时间值的讨论。这些值本质上是特殊格式的字符串。事实上, 我们可以将日期和时间强制转换为字符串类型, 如果字符串是有意义的日期和时间, 也可以反过来进行强制转换。5. 7. 2 表的简 单说明关系模 式说明的 最简单 的格式包 括关键字 CREAT E T ABLE 和随后 的关系 名以 及括号内的一系列属性名及其类型。例 5. 32 我们实 例中 的 MovieStar 关系的关 系模 式, 在 3. 9 节非 正式 地描述 过, 它可以通过图 5. 13 中的语句建成SQL的表。我们将前两 个属性,name和address, 都 说明为字符串。但是对于姓名, 我们决定使用 30 个字符的固定长度字符串, 如果需要就在姓名后面填补空 格, 如果姓 名太长则将其 截至 30 个字符。 相反, 我们将 地址说明 为最多具 有·502·
255 个字符的可变长度字符串。①还不清楚这两种选择是否是可能实现的最好选择, 但是,我们可以用它们来说明两种字符串数据类型。 1) CREAlT E T ABLE MovieStar(2)Name CHAR( 30) ,3) address VARCHAR( 255) ,4) gender CHAR( 1) ,5)birthdate DATE ) ;图 5. 13 说明关系 MovieStar 的关系模式属性gender具有单个字母(M或F) 的值。这样, 我们能够可靠 地使用单个字符 作为该属性的类型。最后, 属性 birthdate 很自然地采用数据类型 DAT E。如果该类型无法从不遵循SQL2 标 准的系 统中得到, 那么 既然所 有的DATE值实际 上都 是 10 个字 符的 字符串—— 8 个数字和 2 个连字符, 我们就可以用 CHAR( 10) 来代替。5. 7. 3 删除表关系作为长期存在的数据库的一部分, 往往只建立一次, 然后与元组共存若干年。关系可能通过把 与 SQL 表不同的某 种格式的 现有数 据转换为 插入命 令序列来 进行成批 写入; 为此, 数据库管理系统(DBMS) 或许会提供成批写入工具。另外, 关系可能随时间的推移而扩展, 日复一日地积累元组。例如, 关系 Movie 最初可能从早期的某个数据库中成批写入, 然后每当一部新的电影发行时通过INSERT操作不断更新。然而, 有时我们希望从数据库模式中删掉某个关系。由于客观条件的变化, 可能不再需 要维 持表 中的 信息。 或 者关 系 就是 暂 时的, 也许 是 在某 个 复杂 的 查 询中 无 法用 单 个SQL 语句来表达时产生的中间结果。如果是这种情形, 我们可以用下面的 SQL 语句来撤消关系RDROP T ABLE R;5. 7. 4 更改关 系模式关系作为长期 存在的数据库的 一部分, 可以 撤消, 而更常见 的是, 不得不 更改现有 的关系模式。这种更改可以通过以关键字 ALT ER TABLE 和关系名开始的语句来完成。有几个选项, 其中最重要的是:1. ADD 加上列名及其数据类型。2.DROP加上列名。例 5. 33 例如, 我们通过增加属性 phone 来更改关系 MovieSt arALT ER T ABLE MovieStar ADD phone CHAR( 16) ;作 为 结 果, 模 式 MovieStar 现 在 具 有 5 个 属 性, 图 5. 13 中 提 到 的 4 个 再 加 上 属 性·602·①数 字 255 并 不是 什么典 型地 址的某 种神 秘表示 法的答 数。单个 字节能 够存 储 0 到 255 之 间的 整数, 所以 能用单个字 节来 表示最 多为 255 个 字符的 可变长 度字 符串的 字符 数, 另外再 加上 存储字 符串 本身的 字节。 然而 , 商业 系统通常能 支持 更长的 可变长 度字 符串。
phone—— 固定长度为 16 个字节的串。在实际的关系中, 元组都会有 phone 对应的分量,但我们知 道现在并没有电 话号码放在那里。因 此, 所有这 些分量值 都将置 为NULL。在5. 7. 5节, 我们将看到如何选用其他的“默认”值来代替未知值 NULL。作为另一例子, 我们可以通过ALT ER T ABLE MovieSt ar DROP birt hdate;来撤消属性birt hdate。 □5. 7. 5 默认值当我们建立或修改元组时, 有时并不能为所有的分量赋值。例如, 上面提到当我们在关系 模式 中增 加一列 时, 现有 的元 组没 有 已知 值, 有人 提出 用 特定 值NULL来 代替“实际”值。或者, 在例 5. 28 中我们提出可 以向关系 Studio 中插入只知 道制片公司名而 不知道地址 和总裁证书号的 新元组。还有, 需要用“不知道”的某 个值来 代替后两 个属性的 实际值。为了说明这些问题,SQL提供了NULL值。除了某些情况下不允许用NULL值( 见6. 2 节) 以外, 该值成为没有给出具体值的所有分量的值。但是, 有时我们宁愿选择其他的默认值, 如果不知道其他值就用该值作为分量值。一般而言, 我们可以在说明属性及其数据类型的地方加上 关键字 DEFAULT 和 一个适当的值。这个值或者为NULL, 或者为某个常量。当然系统也会提供某些其他值, 比如也可以选用当前时间。例 5. 34 让我 们考虑例 5. 32, 我们可能希望用 字符 ’?’作为 未知的gender的默 认值, 也可能希 望用 可能存 在的 最早 的日期, DAT E ' 0000-00-00' 作 为未 知 birt hdate 的 默认值。我们可以将图 5. 13 中的 4) 行和 5) 行替换为:4) gender CHAR( 1) DEFAULT ' ? ' ,5)birt hdate DAT E DEFAULT DAT E '0000-00-00'作为另一个实 例, 当我们 在例 5. 33 中增加 新属性 phone 时, 将 这个新属 性的默认 值设为' unlisted', 则更改语句如同:ALT ER T ABLE MovieStar ADD phone CHAR( 16) DEF AULT ' unlisted' ; □5. 7. 6 域到 目 前为 止, 我 们 已经 为 所有 的 属性 定 义了 数 据 类型。 另一 种 选择 是 首先 定 义 域( domain) , 给数据类 型起一个新的名 称。诸如默认值或将 在 6. 3. 3 节讨论的 对值的约 束等其他信息也可以通过域来说明。然后, 在我们说明属性的时候, 就可在属性名后面用域名取代数据类型。几个属性可以使用同一个域, 以有效的方式把属性联系起来。例如, 如果我们对两个属性使用同一个域, 我们就知道一个属性的值总可以成为另一个属性的值。域定义的格式是关键字 CREAT E DOMAIN、域名、关键字 AS 以及数据类型。 其域说明如下:CREAT E DOMAIN < 域名> AS < 数据类型描述> ;在这些内容之 后可以加上默认 值说明, 也可以 加上将在 6. 3. 3 节讨论的 对域的其 他·702·
约束。例 5. 35 我们为电影的名称定义一个域MovieDomain。该域可以用于关系Movie的title 属性中, 也可以用于关系 StarsIn 的 movieT itle 属性中。可以这样定义该域:CREAT E DOMAIN MovieDomain AS VARCHAR( 50)DEFAULT ' unkown';于是, MovieDomain 域的 值为 最多 50 个 字符 的可变 长度 字符串, 同 时, 对于 未知 的名 称其默认值为' unknown'。当我们说明关系 Movie 的模式时, 可以用: title MovieDomain说明属性 title, 而不用等价的 title VARCHAR( 50)DEFAULT ' unknown'同样, 我们可以用 movieT itle MovieDomain说明关系 StarsIn 中的属性 movieT itle。可以用如下的语句 ALT ER DOMAIN MovieDomain SET DEFAULT ' no such title' ;更改域的默认值:该 更 改 语 句 用 字 符 串 ' no such title' 代 替 我 们 刚 刚 在 例 5. 35 中 说 明 的 域MovieDomain的默认值' unknown'。还可以采用其他的更改选项, 例如将在第 6 章 讨论的对域的约束。我们可以用如下的撤消语句 DROP DOMAIN MovieDomain;来删除域的定义。结果是, 要说明属性时不能再用该域了。但是, 已经用该域定义的属性将如同撤消该域之前一样具有相同的类型和默认值。5. 7. 7 索引关系 中属性 A 的索引 ( index) 是一种 数据结构, 它能 更有效 地查找对 应于属 性 A 具有固定值的元组。索引通常有助于将属性A和常量进 行比较( 例如A= 3, 甚至于A≤ 3)的查询。当关系非常大时, 为了找出与给定条件匹配的元组( 或许很少) , 扫描关系中所有元组的开销将变得很大。例如, 考虑我们在例 5. 1 考察的第一个查询:SELECT *FROM MovieWHERE studioName = ' Disney' AND year = 1990;可能会有 10 000 个电影元组, 其中只有 200 个制作于 1990 年。实 现 该 查 询 的 直 截 了 当 的 方 法 是 取 出 所 有 10 000 个 元 组, 并 对 每 个 元 组 测 试WHERE子句中的条件。如果我们用某种方法只取出制作于 1990 年的 200 个元组, 并对其中每个进 行测试看其制片 公司是否是 Disney, 将有效得 多。如果我们能够 直接得到 满足WHERE子句中的两个条件 —— 制片公司为Disney同时制作年份为 1990 年—— 的大·802·
约 10 个左右元组, 甚至会更有效。但这已超过从通常使用的数据结构所能期望得到的了。尽管建立索引并不是包括SQL2 在内的任何SQL标准的一部分, 但大多数商业 系统有办法向数据库设计者表明, 系统能够为某个关系中的某个属性建立索引。下面的句法是有代表性的。假如我们想要得到关系Movie中的属性year的索引, 就可以如下表达:CREATE INDEX YearIndex ON Movie( year) ;结果是建立了关系Movie的year属性的索引, 索引名为YearIndex。从此, 对于指定了年份的 SQL 查询, SQL 查 询处理程 序将采 用只检查 关系 Movie 中具有 指定 年份的 元组 的方法, 这样执行的结果是减少了回答查询所需要的时间。通常, 还可以利用多属性索引。该索引将取出若干属性的值, 并且能有效地查找对于这些属性具有给定值的元组。表面上看多属性索引没有单属性索引有用, 因为当前者并没有对每个属性都赋值时就要使用后者。但是, 当可以用多属性索引时, 就会更有效地找到所要求的元组。例 5. 36 由于 属性 title 和 year 构成 关系 Movie 的键码, 通常 我们可能 希望这两 个属性值都指定或者都不指定。下面是关于这两个属性的索引的典型说明:CREAT E INDEX KeyIndex ON Movie( title, year ) ;由 于(title,year) 是键 码, 那么 每当给 出一 组名称 和年 份, 我们确 信索 引 只能 找到 一个元组, 这也正是我们所要求的元组。相反, 如果查询既指定了名称, 又指定了年份, 但只能利用YearIndex索引, 那么系统顶多能返回该年份的所有电影, 然后从中对给定的 名称进行检查。如果我们有单独的tit le索引, 情形就要比只有year索引好。原因就是, 对于给定的年份会有许多电影, 而对于给定的名称通常只有很少的电影。在此特例中, 对于给定的名称,返回所有的元组然后从中检查给定的年份, 比使用既包括title又包括year的多属性索引花费的时间稍多一点。 □如果我们想要撤消索引, 只需简单地把索引名用在如下的语句中:DROP INDEX YearIndex;数据库设计者需要折衷考虑索引的选择:· 某一属性索引的存在能够大大加快该属性值已然确定的查询。· 另一 方面, 所有基于 某个关 系的某个 属性的 索引都使 得对该关 系的 插入、删除 和修改操作变得更加复杂和费时。索引的选择是 数据库设计中最 困难的部 分之一, 因为需 要估计 出数据库 中查询和 其他操作有代表性的混合方式。如果对关系的查询比更新频繁得多, 那么对最频繁指定的属性建立索引将很有意义。如果更新是主要的行为, 那么我们对建立索引就应非常谨慎。即使如此, 对频繁使用的属性建立索引也会提高效率。事实上, 由于某些更新命令涉及到对数 据 库 的 查 询 ( 如 带 有select-from-where子 查 询 的INSERT操 作, 或 带 有 条 件 的DELET E 操作) , 人们必须很仔细地对更新和查询操作的相关频率进行估算。5. 7. 8 本节练 习练习 5. 7. 1: 在本节, 我们只对不断滚动的实例中五个关系中的MovieSt ar关系给出了正·902·
式的说明。给出其余四个关系的适当的说明:Movie(tit le,year,length,inColor,st udioName,producerC# )St arsIn ( movieT itle, movieYear, starName)MovieExec(name,address,cert# ,netWorth)St udio (name, address, presC# )练习 5. 7. 2: 下面, 我们又一次重复了练习 4. 1. 1 中非正式的数据库模式:Product ( make, model, type)PC(model,speed,ram,hd,cd,price)Laptop (model, speed, ram, hd, screen, price)Print er(model,color,type,price)写出下列说明:(a) 与关系Product相对应的模式。( b) 与关系 PC 相对应的模式。* (c) 与关系Lapt op相对应的模式。( d) 与关系 P rinter 相对应的模式。(e) =对域ModelT ype的适当定义, 其值为型号(model) 。给出在从(a) 到(d) 的模式中如何使用该域。* (f) 7对(c) 中你写的Laptop模式进行更改, 增加属性cd。如果便携式电脑没有配置光驱, 则令该属性对应的默认值为 ' none' 。(g) 更改(d) 中的Print er模式, 删掉其属性color。练习 5. 7. 3: 这里给出了练习 4. 1. 3 中的非正式的模式:Classes(class,type,country,numGuns,bore,displacement)Ships ( name, class, launched)Battles(name,date)Outcomes ( ship, battle, result)写出下列说明:( a) 与关系 Classes 相对应的模式。(b) 与关系Ships相对应的模式。( c) 与关系 Battles 相对应的模式。(d) 与关系Out comes相对应的模式。( e) 对域 ShipNames 的适当定义, 使之能够用于舰艇名称和等级名称。利用该域更改(a) 、(b) 和(c) 中的模式。( f) 对 ( b) 中 的关 系 Ships 进 行 更 改, 使 之 包 含属 性 yard, 用于 给 出 建 造 舰 艇 的 造船厂。( g) 更改( a) 中的关系 Classes, 删掉属性 bore。! 练习 5. 7. 4: 解释下列两条语句之间的区别。 DROP R; DELET E FROM R;·012·
关系(relation) 、表(table) 和视图(view)SQL程 序 员 倾 向 于用 术 语“表”来代 替“关 系”。原 因 是 对实 际 存 储 的 关系 ( 即“表”) 和虚拟的关系( 即“视图”) 二者作出明确的区别是非常重要的。既然我们知道了表和 视图之 间的 区别, 那么 我们 应该仅 在或 者使 用表或 者使 用视图 的情 况 下才 使用“ 关系”。当我 们想 要强调 是存 储的 关系而 不是 视图时, 有 时会 使用术 语“基本 关系 ”(base relation) 或“基本表”(base table) 。还有第三种关系, 既不是视图也不是永久存储的表。这些关系是临时结果, 可能是为某个子查询而构造出来的。以后把临时结果也称为“关系”。5. 8 视 图 的 定 义通过CREAT E T ABLE语句定义的关系实际存在于数据库中。也就是说,SQL系统把表(table) 存储在某种物理的体系结构中。可以认为表能够无限期地存在并且不发生变化, 除非明确表示用 INSERT 或 5. 6 节讨论的 其他更新语句之一 对其进行改变。在 这个意义上来说表是持久不变的。还有另一类并不实际存在的SQL关系称为“视图”(view) 。相反, 它们通过非常类似于查询的表达式来定义。反过来, 又可以对视图进行查询, 就如同它们实际存在一样, 并且在某些情况下, 甚至可以更新视图。5. 8. 1 视图的 说明定义视图的最简单格式为:1. 关键字 CREAT E VIEW,2. 视图名,3. 关键字 AS, 以及4. 查询 Q。该查询就是视图的定义。每当我们对视图进行查询时, SQL 的作用就好像当时在执行Q, 并对Q所生成的关系进行查询。即, 简单的视图定义格式如下:CREAT E VIEW〈视图名〉AS〈视图定义〉;例 5. 37 假定我们想要把关系Movie ( title, year, length, inColor , studioName, producerC# )的一部分, 确切地说, 由Paramount( 派拉蒙) 制片公司制作的电影的 名称和年份作为 一个视图, 我们可以通过以下语句来定义视图。 1)CREAT E VIEW ParamountMovie AS 2)SELECT title,year 3) FROM Movie 4)WHERE studioName=' Paramount';·112·
首先, 就像我们在 1) 行看到的, 视图名为 P aramountMovie, 视图的属性在 2) 行列出,名称为title和year。视图的定义在 2) 到 4) 行。 □5. 8. 2 视图的 查询关 系 ParamountMovie 并 不 包 含 通 常 意 义 下 的 元 组。 相 反, 如 果 我 们 查 询ParamountMovie, 将会从基本表Movie中得到适当的元组, 所以查询能够得到回答。即使我们并没有表现出对 ParamountMovie 有什么改变, 只是因为其基本表可能在两次查询期间发生变化, 结果, 我们对ParamountMovie进行两次同样的查询可能得到不同的回答。例 5. 38 我 们可 以对视 图 Paramount Movie 进行查 询, 就如 同它 是一 个 存储 的表,例如:SELECT tit leFROM ParamountMovieWHERE year = 1979;用视 图ParamountMovie的 定义 将上 面的 查 询转 换 为只 访 问基 本 表Movie的 新 查询。我们将在 5. 8. 5 节阐明如何将基于视图的查询转换为基于基本表的查询。然而, 在这种简单 的情况 下, 不 难推断 出该 例中视 图查 询的意 义。我 们观察 到ParamountMovie同Movie 的区别只在于以下两点:( 1)ParamountMovie只生成属性tit le和year。( 2) 条 件 studioName = ' Paramount' 是 关于 P aramountMovie 的任 何 WHERE 子句的一部分。由 于 我 们 的 查 询 只 想 生 成 title, ( 1) 不 存 在 问 题。对 于 ( 2) , 我 们 只 需 要 将 条 件studioName=' Paramount'引入到查询的WHERE子句中。这样, 我们就可以在FROM子句中用 Movie 来代替 ParamountMovie, 从而保证把查询的含义保存下来。于是, 查询SELECT titleFROM MovieWHERE st udioName=' Paramount' AND year= 1979;就成为 对基本表 Movie 的查 询, 与 我们最初 对视图 ParamountMovie 的查 询具有 相同 的效果。注意, 做这种转换是SQL系统的工作。我们说明该推导过程只是想指出对视图的查询的含义。 □例 5. 39 也可以写出既涉及到视图又涉及到基本表的查询。例如SELECT DIST INCT st arNameFROM ParamountMovie,St arsInWHERE title = movieT itle AND year = movieYear;该查询寻找由派拉蒙制作的电影中所有影星的姓名。注意, 即使影星们出现在多部派拉蒙制片公司制作的电影中, 由于使用了 DIST INCT 就保证影星只列出一次。 □例 5. 40 让我们来考虑用于定义视图的更复杂的查询。我们的目标是得到具有电影名及其制片人名的关系 MovieProd。定义视图的查询既涉及到关系Movie(title,year,length,inColor,studioName,producerC# )·212·
从中得到制片人的证书号, 又涉及到关系MovieExec(name,address,cert# ,netWorth)在此我们将证书号和姓名联系起来。我们可以这样写: 1)CREAT E VIEW MovieProd AS2) SELECT title, name3)FROM Movie,MovieExec4) WHERE producerC# = cert # ;我们可以对该视图进行查询, 就如同它是一个存储的关系。例如, 要找到“Gone Withthe Wind”《乱世佳人》的制片人, 可询问SELECT nameFROM MovieProdWHERE title=' Gone With the Wind';就像对任何视图一样, 把该查询看作只是针对基本表的等价查询, 如同 SELECT name FROM Movie, MovieExecWHERE producerC# =cert#AND title=' Gone With the Wind'; □5. 8. 3 属性改 名有时, 我们会更愿意给出自己选择的视图属性名, 而不愿使用由定义视图的查询产生的属性名。我们可在CREAT E VIEW语句的视 图名之后用括号内 的属性表来指定 视图的属性。例如, 我们可以这样重新写出例 5. 40 的视图定义:CREAT E VIEW MovieProd(movieT it le,prodName)ASSELECE title, nameFROM Movie,MovieExecWHERE producerC# = cert # ;视图是一样的, 但各列的标题用属性movieT it le和prodName来代替tit le和name。5. 8. 4 视图的 更新在有限的情形下, 可以对视图进行插入、删除或者修改操作。首先, 由于视图并不像基本表( 存储的关系) 那样实际存在, 这种概念毫无意义。比如说, 将新的元组插入到视图中意味着什么呢? 该元组去向如何? 数据库系统如何记住该元组应该在视图中呢?对于许多视图, 回答仅仅是“你不能这样操作”。然而, 对于足够简单的视图, 称为“可更新”视图, 就 可能将对视图的 更新转换为等 价的对基 本表的更 新, 于是可以 用对基本 表的操作来代替更新操作。SQL2 对于何时允许对视图进行更新提供了正式的定义。SQL2的规则很复杂, 但是粗略地讲, 它允许更新从单个关系 R 中选出某些属性( 用 SELECT 而不用SELECT DIST INCT) 定义的视图( 而R本身也可以为可更新的视图) 。两个重要的技术要点为:·WHERE子句绝对不能在子查询中涉及到R。·312·
· SELECT 子句中的属性必须包括足够的属性, 以便对于每个要插入到视图中 的元组, 我 们都可以将其他的 属性赋以 NULL 值或其他适 当的默认值, 就能得到基 本关系的一个元组, 而该元组将生成要插入到视图中的元组。例 5. 41 假定我们要向例 5. 37 的视图ParamountMovie中插入如下的元组:INSERT INT O P aramountMovieVALUES( ' Star T rek' , 1979) ;由于视图 ParamountMovie 只对单个基本表Movie( t itle, year, length, inColor, studioName, producerC# )中的某些元组的某些分量进行查询, 因此基本上满足SQL2 可更新的条件。唯一的问题是, 由于关系Movie中的属性st udioName并不 是视图的属性, 那么插 入到Movie中的元组将用NULL而不是' Paramount'作为st udioName的值。因此, 为使 视图 P aramountMovie 可更 新, 我们 应该 在视 图 的 SELECT 子句 中增 加属性 studioName, 即使对我们来说制片公司的名称显然为派拉蒙。视图 ParamountMovie修订后的定义为:  ̄1)CREA;T E VIEW ParamountMovie AS2) SELECT studioName, title, year3) FROM Movie4)WHERE studioName=' Paramount';于是, 我们可以将对可更新视图ParamountMovie进行插入的操作写为:INSERT INT O ParamountMovieVALUES( ' Paramount' , ' Star T rek' , 1979 );为了实 现该插入, 当视 图定 义用于 关系Movie时, 我们 构造 出一 个Movie元 组使 之生 成 插 入 到 视 图 中 的 元 组。 对 于 上 面 特 定 的 插 入 操 作, 其 studioName 分 量 为' Paramount' , tit le 分量为 ' St ar T rek' , 而 year 分量为 1979。在插入 的Movie元组 中必须 有视图中 没有出 现的其他 三个属 性——length,inColor和producerC# 。可是, 我们无法推断出它们的值。结果, 新的Movie元组必须为这三个属性中的每个相应分量赋以适当的默认值: 或者为 NULL 或者为某个 其他的对属性或 相应域所说明的默认值。例如, 如果对属性 length 说明的默认值为 0, 而其他两个用 NULL 作为其默认值, 那么结果插入的Movie元组就会是:title Year Length inColor studioName producerC#'Star Trek'1979 0 gNULL ' Paramount' NULL□ 我们还可以对可更新视图进行删除操作。删除操作, 和插入操作一样, 将传递给底层的关系 R, 从而导致删除和视图中要删除的元组相对应的 R 中的所有元组。例 5. 42 假 设 我 们 希 望 从 可 更 新 视 图 ParamountMovie 中 删 除 所 有 名 称 中 含 有“T rek”的电影。我们可以写出删除语句:DELET E FROM Paramount MovieWHERE title LIKE '%Trek%';·412·
的社会保险号对应的元组。由于这样的帐户可能有几个, 我们不得不查找所有的元组。典型的银行可能会有许多帐户, 因而Accounts关系也必将占据许多磁盘块。检查所有 的元组付出的代价就太大了。如果 Customers 关系中有姓名索引, 那么查询就容易多了。我们只需要使用索引找到含 有Sally Jones的 元 组所 在的 磁 盘 块, 而不 是 查 找整 个Cust omers关 系。正 如我 们 在1. 2. 1节 的方框内所讲的, 为了找到 想要的索 引, 典 型的平衡 树索引 需要查找 三个索引 磁盘块。①我们再访问一个磁盘块, 就可找到 Sally Jones 所在的元组。当然, 我 们还需要做第二步: 在 Accounts 关系中寻找 Sally Jones 的社会保险号 对应的帐户。这一步往往需要访问磁盘很多次。但如果针对Accounts关系存在一个社会保险号索引, 那么检索该索引我们就能找到对应于给定社会保险号的帐户所在的每个磁盘块。为了做到这一点, 就像我们讨论过的检索 Customers 关系的索引那样, 必须用两到三次磁盘访问来检索索引。如果所要的元组分布在不同的磁盘块上, 也许我们需要逐个访问这些磁盘块。但很可能, 一个Customers没有那么多的帐户, 因此, 这步操作可能只包含几次磁盘访问。如果这两个索引都有的话, 也许通过 6~10 次磁盘访问, 我们就能回答上述查询了。如果只有一个索引或者两个索引都没有, 而不得不用比较差的查询方案, 那么, 由于我们要扫描整个关系, 而关系的规模又很大的话, 磁盘访问的次数就可能是几百或几千的数量级。 □也许 例 1. 2 会误 导出一 个结 论—— 查 询优化 所做 的一 切不 过是 使用 现 有的 索引 而已。事实上, 这方面还有很多的内容。复杂的查询往往允许我们改变操作顺序, 也许会有很多种可能的查询方案, 一般其数量是查询规模的指数函数。有时我们能选择使用一个索引, 但不能使用两个。这方面的研究是数据库管理系统实现的重要方面之一, 但超出了本书的讨论范围。1. 2. 4 事务管 理程序我们在 1. 1 节已经提到, DBMS 必须对执行数据库操作提供一些特殊的保障。例如,我们 曾讨 论过, 即使 在面 临严 重的系 统故 障时, 操作 结果 也不能 丢失 的重 要性。 典型 的DBMS允许用户将一个或多个查询和/ 或更新组成事务(transaction) 。事务, 非正式地讲,是一组按顺序执行的操作单位。数据库系统 常常允许许多事 务并发地 执行。例如, 有些 事情可 能在一家 银行的所 有AT M机器上同时执行。保证这些事务全都正确执行是DBMS中事务管理程序的任务。更详细地说, 事务的“正确”执 行还 需 要通 常称 为ACID的 特性。ACID取自于事务执行的四个主要需求的首字母。这四个特性是:· 原子性( atomicity) 。我们需要整个事务或者都执行或者都不执行。例如, 从 AT M机器中取钱和记入相关借方的顾客帐户上应该是一个原子事务。如果钱已经付出了而没有记入借方帐户或者记入了借方帐户而钱并没有付出, 都是不能接受的。·8·①实 际上, 由于 平衡 树的根 节点 在涉及 该索引 的每 次查找 中都 要用到 , 所 以它 所在的 磁盘 块 常在 主存 中占 据一个缓冲 区页 面, 这样, 通常 两次 磁盘块 访问 就足够 了。
为何有些视图不可更新请考虑例 5. 40 中的视图MovieProd, 它将电影名称和制片 人的姓名联系到 一起。由于 在 F ROM 子句 中有 两个关 系: Movie 和 MovieExec, 根 据 SQL2 的定 义, 该 视图不可更新。假设我们试图插入如下一个元组:( ' Greatest Show on Earth' , ' Cecil B. DeMille' )我们就 不得不 将元 组既插 入到Movie中, 又 插入到MovieExec中。 我们可 以对lengt h 或 address 这类属 性使用 默认值, 但是对 于都表 示 DeMille 未 知证书号 的等价属性producerC# 和cert# 怎么办呢? 我们可能对这两个属性都使用NULL值。但是,当连接含有 NULL 的两 个关系时, SQL 无法识别 出两个 NULL 值是否相等( 见 5. 9.1 节) 。因 此, 在 视 图MovieProd中,' Greatest Show on Earth'就 无 法 同' Cecil B.DeMille' 联系起来, 这表明我们的插入操作不能正确执行。该 删 除 语 句 将 转 换 为 等 价 的 对 基 本 表Movie的 删 除; 唯 一 的 区 别 就 是 把 视 图ParamountMovie 定义中的条件加到 WHERE 子句的条件中。DELETE F ROM MovieWHERE title LIKE ' % T rek% ' AND studioName = ' Paramount' ;这就是最终的删除语句。 □与此类似, 对可更新视图的修改也将传递给底层的关系。因此, 修改视图的结果就是对导致修改视图元组的底层关系的所有元组进行修改。例 5. 43 视图的修改UPDAT E ParamountMovieSET year= 1979WHERE title = ' Star T rek the Movie' ;将转换为对基本表的修改 U PDAT E MovieSET year = 1979WHERE tit le=' Star T rek the Movie' AND StudioName= ’Paramount'; □最后一种对视图的更新就是完全撤消视图。无论视图是否可更新, 这种更新都可以进行。典型的DROP语句为DROP VIEW ParamountMovie;注意该语句撤 消了视图的定义, 因此我 们再进 行查询或 发布更 新命令时 都不能涉 及到该视图。然而, 撤消视图并不影响底层的Movie关系中的任何元组。相反,DROP T ABLE Movie不仅会使得表Movie消失, 而且也会使视图ParamountMovie不能再用, 这是由于对 该视图的查询会间接地引用已经不存在的关系Movie。·512·
5. 8. 5 对涉及 到视图的 查询的解释虽然如何由 SQL 系统实现对视图的查询超出了本书的范围, 但是我们可以通过 下面的解释过程来理解视图查询的含义。尽管通过增加反映SQL的附加特征的运算符能处理完整的 SQL, 如分组 和聚合, 我们还 是应该 将自己限 制在 能用 关系代 数表 示的查 询和 视图的范围之内。图 5. 14 说明了基本的想法。图中, 查询 Q 由关系代数中的表达树来表示。该表达树用表示视图的一些关系作为叶子。我们假定有两片这样的叶子, 视图V和W。为了从基本表的观点解释Q, 我们就要找出视图V和W的定义。这些定义也是由关系代数中的表达树来表示的。要想形成对基本表的 查询, 我们 将Q树中作 为视图的每片叶子 用视图定义树的 副本的根来代替。因 此, 在图 5. 14 中我 们描绘出标明 V 和 W 的叶子由这些 视图的定义来 代替。结果生成的树成为与最初对视图的查询等价的对基本表的查询。例 5. 44 让 我 们 考 虑 例 5. 38 中 的 视 图 的 定 义 和 查 询。 回 顾 一 下, 视 图ParamountMovie的定义为:  ̄1) CRE AT E VIEW ParamountMovie AS2)SELECT title,year3) FROM Movie4)WHERE sut dioName=' P aramount';图 5. 15 给出了定义该视图的查询表达树。图 5. 14 用视图的定义代替视图的引用 图 5. 15 视图 ParamountMovie 的表达树例 5. 38 的查询为:SELECT tit leFROM ParamountMovieWHERE year= 1979;查找派拉蒙制片公司 1979 年制作的电影。该查询可用图 5. 16 中给出的表达树来描述。注意该树的一片叶子表示视图ParamountMovie。因此, 我们可 以用 图 5. 15 中 的树 代替 图 5. 16 中 的叶 子ParamountMovie来 解释 查询。结果树在图 5. 17 中给出。用图 5. 17 中的树对查询进行解释是可以接受的。但是, 这种解释方式过于复杂。SQL系统会对这 种树进行转换, 目的是使其 看起来像我 们在例 5. 38 中提出的 查询表 达式( 译注: 原文误为表达树) :·612·
SELECT titleFROM MovieWHERE sut dioName=' Paramount' AND year= 1979;πtit le©¦σyear= 1979©¦ParamountMovie图 5. 16 查询表达树πtit le©¦σyear= 1979©¦πtitle, year©¦σstudioName= 'P aramount '©¦Movie图 5. 17 根据基本表查询的示意图例如, 我们可以把投影πtit l e, year 移到选择σy ear = 1979 之上。原因就是对投影的延时绝对不会改变表达式的含义。这样, 就 在一行中有了两个 投影, 首 先投影到title和year, 然 后仅投影到 title。显然第一个投影是冗余的, 我们可以将其删除。于是, 这两个投影就可 以用tilte 上的单个投影所代替。两个选择也 可 以 组 合在 一 起。 一般 而 言, 连 续 的 两 个 选择 可 以 用 两 个条 件 的“与”(AND) 构成的一个选择来代替。结果的表达树如图 5. 18 所示。我们可以从如下查询直接得到该树: SELECT titleFROM MovieWHERE st udioName=' P aramount' AND year= 1979; □πtitl e©¦σyear= 1979 AND st udioName= ' Paramount'©¦Movie图 5. 18 简化对基本表的查询5. 8. 6 本节练 习练习 5. 8. 1: 根据我们不断滚动的实例中的基本表:Movie( title, year, length, inColor, studioName, producerC# )MovieExec(name,address,cert# ,netWorth)Studio( name, address, presC# )构造下列视图:* (a) R视 图RichExec给出 净资 产至 少 10 000 000 美元 的所 有行 政长 官 的姓 名、地址、证书号和净资产。·712·
( b) 视图 StudioPres 给出身为制片公司总裁的所有行政长官的姓名、地址和证书号。(c) A视图ExecutiveStar给出既 是行政长官又是 影星的人的姓 名、地 址、性 别、出 生日期、证书号以及净资产。练习 5. 8. 2: 练习 5. 8. 1 的视图中, 哪些是可更新的?练习 5. 8. 3: 用练习 5. 8. 1 中的一个或多个视图而不用基本表, 写出下列所有查询:(a) 找出既是影星又是行政长官的女性的姓名。* ( b) 找出既是制片公司总裁而资产又至少 10 000 000 美元的行政长官的姓名。! (c) 找出既是影星而资产又至少 50 000 000 美元的制片公司总裁的姓名。* ! 练习 5. 8. 4: 对于例 5. 40 中的视图和查询:(a) 给出视图MovieProd的表达树。( b) 给出该例的查询表达树。(c) 按照你写的(a) 和(b) 的答案构造出根据基本表的查询表达式。( d) 说明如何改变你写的( c) 的表达 式使之成为符合 例 5. 40 中 给出的解答的等 价表达式。! 练习 5. 8. 5: 对 于练习 5. 8. 3 中的 每个查 询, 用 关系代数 表达式 表示出查 询和视 图, 代替查询表达式中使用的视图, 并尽可能简化结果表达式。写出与你写的基于基本表的结果表达式相对应的 SQL 查询。练习 5. 8. 6: 用练习 4. 1. 3 中的基本表Classes ( class, type, country, numGuns, bore, displacement)Ships(name,class,launched)( a) 3定义视图 Brit ishShips, 为每 一艘英 国舰艇给 出其等 级、类 型、火 炮数量、口径、排水量和下水年份。( b) @利用( a) 中的 视图 写一个 查询, 找 出 1919 年以 前下 水的所 有英 国战列 舰的 火炮数量和排水量。! ( c) '将( b) 中的查询和( a) 中的视图表示成关系代数表达式, 用来代替查询表达式中使用的视图, 并尽可能简化结果表达式。! ( d) 写出与( c) 中的基于基本表 Classes 和 Ships 的表达式相对应的 SQL 查询。5. 9 空值和外部连接在本节我们 将进一步学习用 NULL 作为 SQL 元组中的一个 值的有关内 容。NULL值特别重要的 应用在于定义变 体的连接运算, 这种 运算在一 个关系 的某个元 组未能和 另一个关系的任一元组连接的情况下并不丢失信息。这种变体的连接称为“外部连接”。在本节我们将涉及到外部连接运算符的几种变体。尽管NULL存在于早期的SQL标准中,但外部连接运算符一般只存在于支持 SQL2 的系统中。5. 9. 1 对空值 的运算我 们 已 经 多 次 看 到SQL支持称为NULL的特殊值。在 4. 7. 4 节讨论了NULL值的·812·
关于空值的缺陷这是一 个很诱 人的假设, 即SQL2 中 的NULL总可 以用 来表示 这样 的含 义:“一个确实存在但是我们还不知道的值”。然而, 有一些情形是和直觉相违背的。例如, 假设x是某元组的一个分量, 该分量的域是整数。由于无论x是什么整数, 它和 0 的乘积都是 0, 因此我们可以推断出 0* x 的值必定为 0。但是, 如果 x 的值为 NULL, 并采用5. 9. 1 节的规则 1, 那么 0 和NULL的乘积是NULL。与此相似, 因为无论x是什么整数, 它和自己的差都是 0, 因此我们可推断出 x- x 的值为 0。但是, 又一次采用规则 1,结果却是NULL。一些用途, 比如表示未知的或不存在的值。例如, 当我们向关系中插入元组时如果所用的命令只提供元组的某些分量而不是所有分量, 那么 就会生成NULL值。除非对没有 指定值的分量有另外说明的默认值, 否则该分量就会是 NULL 值。我们 将看到外部连接 也是NULL值的另一种来源。当我们对 NULL 值进行运算时必须记住两条重要的规则:1. 当我们 对 NULL 值 和其他任何值( 包括另 一个 NULL 值) 进行 运算时, 使用类 似于× 或+ 的算数运算符, 结果为NULL。2. 当我们 将 NULL 值 和任何值( 包括另一个 NULL 值) 进 行比较 时, 使 用类似于 =或> 的比较运算符, 结果为U NKNOWN。值UNKNOWN和T RUE或FALSE类似, 是另一种真值; 我们将简略地讨论如何使用UNKNOWN值。但是, 我们必须记住, 尽管 NULL 值能够出现在元组中, 但它不是常量。因此, 当我们试图对 值为NULL的表 达式进行 运算 而使用 上述 规则 时, 不 能显 式地 将NULL作为 操作数使用。例 5. 45 假设 x 值为 NULL。那么 x+ 3 的值同样为 NULL。但是, NULL+ 3 不是合法的SQL表达式。与此相似,x= 3 的值 为UNKNOWN, 这是由于我们 无法说出x的值( 为 NULL) 是否等于 3。然而, 比较运算 NULL= 3 不是正确的 SQL 表达式。 □顺便提一下, 询问x是否具有NULL值的正确方法是采用表达式x IS NULL。如果x 具有 NULL 值, 则表达式取 T RUE 值, 否则表达式取 FALSE 值。与此相似, x IS NOTNULL 取值为真, 除非 x 的值为 NULL。5. 9. 2 真值UNKNOWN在 5. 1. 2 节我 们 观察 到 比较 的 结果 或 者 为T RUE, 或者 为FALSE, 并且 这 些 真 值(trut h-value) 可以用逻辑运算符AND、OR和NOT以显式的方法组合起来。我们刚才看到当 NULL 值出现时, 比较运算可以产生第三种真值: UNKNOWN。现在我们必须 了解在所有三种真值组合中, 逻辑运算符的具体运算结果。如果我 们将T RUE看作 1( 也就是 完全的真) , 将FALSE看作 0( 也就是 完全没有 真的成分) , 而将 UNKNOWN 看作 1/ 2( 也就是在真与假之间) , 规则就很容易记住了。即:1. 两个真值的AND结果是二者的最小值。也就是说, 如果x或者y为FALSE, 那么·912·
x AND y 结果 为 FALSE ; 如果二 者都 不为 FALSE 但 至少有 一个 为 UNKNOWN, 则 结果为UNKNOWN; 只有当x和y都为T RUE时结果才为T RUE。2. 两个真值的OR结果是二者的最大值。也就是说, 如果x或者y为T RUE, 那么xOR y 结 果为 T RUE; 如果 二 者都 不为 T RUE 但 至少 有 一个 为 UNKNOWN, 则结 果 为U NKNOWN; 只有当二者都为FALSE时结果才为FALSE。3. 真值的非是 1 减去该真值。也就是说, 当x为FALSE时NOT x结果为T RUE,当 x 为 T RUE 时结果为 FALSE, 而当 x 为 UNKNOWN 时结果为 UNKNOWN。图 5. 19 汇总了操作数 x 和 y 的 9 种不同的真值组合用 3 种逻辑运算符运算的结果。最后一个运算符NOT的值仅取决于x。x y x AND y x OR y NOT xTRUE TRU E TRUE TRU E FALSETRUE U NKNOWN UNKNOWN TRU E FALSETRUE FALSE FALSE TRU E FALSEUNKNOWN TRU E UNKNOWN TRU E UNKNOWNUNKNOWN U NKNOWN UNKNOWN UNKNOWN UNKNOWNUNKNOWN FALSE FALSE UNKNOWN UNKNOWNFALSE TRU E FALSE TRU E TRUEFALSE U NKNOWN FALSE UNKNOWN TRUEFALSE FALSE FALSE FALSE TRUE图 5. 19 三值逻辑的真值表把select-from-where语 句或DELET E语句的WHERE子句 中出 现的SQL条件 用于 某 个 关 系 的 每 个 元 组, 对 于 每 个 元 组, 都 会 产 生 3 个 真 值 T RUE、FALSE 或U NKNOWN之一。但是, 只有条件值为T RUE的元组才能成为回答的一部分, 而把具有U NKNOWN或FALSE值的元组从回答中排除掉。这种情形导致了与在方框“关于空值的缺陷”中讨论的情形相似的另一种出乎意外的状态。例 5. 46 假定我们向不断滚动的实例中的关系Movie(title,year,length,inColor,studioName,producerC# )进行下面的查询:SELECT*FROM MovieWHERE length< = 120OR length> 120;直觉 上看, 我们 会期 望得到 关系Movie的 一个副 本, 因为 每部电 影的 长度均 为或 者120 分钟或更短, 或者比 120 分钟更长。但是, 假定一些Movie元组的length分量为NULL。那么比较运算length< = 120和lengt h> 120 的 值 都 为UNKNOWN。根 据 图 5. 19, 两 个U NKNOWN的OR还 是U NKNOWN。 因 此, 对 于length分 量 为NULL的 任 何 元 组,WHERE子 句 的 值 为·022·
U NKNOWN。这样的元组不能作为查询结果的分量返回。结果, 查询真正的含义为“找出所有具有非NULL长度的Movie元组”。 □5. 9. 3 SQL2 中的连接表达式在介绍SQL2 的外部连接运算之前, 我们先来考虑传统连接的简单情况。SQL2 中有几 种连 接 运 算 符 可 以 利 用; 而 以 前 的 SQL 标 准 并 未 明 确 包 含 这 些 运 算 符, 不 过, 通 过select-from-where查询可能 获得相 同的效果。在SQL2 中, 连 接表达 式可以代 替select-from-where 并且可以用在允许 select-from-where 查询的任何地方。此外, 由于连接表达式可以生成关系, 所以, 也可以用在select-from-where查询的FROM子句中。连接表达式最 简单的格式为 CROSS JOIN; 该 术语与我 们在 4. 1. 4 节所 称的笛卡 尔积或简称的“乘积”(product) 是同义词。例如, 如果我们想要如下两个关系的乘积Movie( t itle, year, length, inColor, studioName, producerC# )StarsIn(movieT itle,movieYear,st arName)我们可以表示为Movie CROSS JOIN StarsIn;结 果 为 具 有 关 系Movie和St arsIn所 有 属 性 的 9 列 的 关 系。 由Movie的 一 个 元 组 和StarsIn 的一个元组组成的每一对都构成结果关系的一个元组。乘积关系中的属性可以称为R.A, 其中R是两个相连的关 系之一,A是其属性之一。如果只有一 个关系具有 命名为 A 的 属性, 那么 R 和 圆点照 常可以省 略掉。该例 中, 由 于Movie和StarsIn没有共同的属性, 在乘积中 9 个属性名足够了。然而, 和自己的乘 积却几乎 是无意 义的操作。 用关键字 ON 可以 得到更常 用的 θ连接。我们在两个关系名R和S之间写上JOIN, 并在它们后面加上ON和条件。先做R×S的乘积运算, 随后按ON和后面的任何条件进行选择。例 5. 47 假定我们想要连接关系Movie(title,year,length,inColor,studioName,producerC# )StarsIn( movieT itle, movieYear , starName )条件为要连接的元组都指向同一部电影。也就是说, 来自两个关系的名称和年份都必须相同。可以通过Movie JOIN StarsIn ONtitle = movieT itle AND year = movieYear;进行该查询。结果又是具有显式属性名的 9 列关系。但是, 现在只有当一个来自Movie的元组和 一个 来自 StarsIn 的元 组在名 称和 年份 上都一 致时, 两 个元组 才能 组合形 成一 个结果元组。由于每个结果元组的 title 和 MovieTit le 两个分量具有相同的值, 并且 year 和movieYear两个分量也具有相同的值, 结果, 其中的两列是冗余的。 □前面已经提到 过, 连接表 达式可以出现在 select-form-where 查询的 FROM 子句中。如果 这样, 由连 接表 达式所 表示 的关系 就将 如同FROM子 句中 的基 本表 或 视图 一样 处理。这种运用的一个实例如下。例 5. 48 如果我 们关注 例 5. 47 中有两 个冗余 分量的事 实, 我 们可以 将该例 中的 整·122·
个表达式放在 FROM 子句中, 并用 SELECT 子句除去不必要的属性。于 是, 我们可 以这样写:SELECT title, year , length, inColor, studioName, producerC# , starNameFROM Movie JOIN StarsIn ONtitle = movieT itle AND year = movieYear;从而得到 7 列 的关系, 该 关系是把关系Movie中的每个元组 都通过该电影中 的影星以 所有可能的方式进行扩充而得到的。 □5. 9. 4 自然连 接我们回顾 4. 1. 5 节, 就会看到自然连接(natural join) 与θ连接的不同之处在于:1. 连接条件是两个关系中具有公共名字的所有属性对都相等, 不需要其他条件。2. 对每个相等属性对之一进行投影。SQL2 的自然连接就完全按这种方式工作。关键字 NAT URAL JOIN 出现在两个关系之间表示运算符。例 5. 49 假定我们想要计算关系MovieStar(name,address,gender,birt hdate)MovieExec ( name, address, cert # , netWorth )的自然连接。结果将是一个关系, 其模式中包括属性name和address加上出现在两 个关系之一的所有属性。结果元组所表示的人既是影星又是行政长官, 元组中含有与两者有关的所有信息: 姓名、地址、性别、出生日期、证书号和净资产。表达式MovieStar NAT URAL JOIN MovieExec;简明地描述了所需要的关系。 □5. 9. 5 外部连 接外部连接是 SQL2 标准 提供的一 种连接 的变体, 用来处 理在特 定的连接 情况下的 下列问题。假设我们希望计算连接RS。如果R中的一个元组t与S中的任何元组都不匹配, 那么 t 将从关系 R S 中消失。这种情况由于种种原因而显得不灵活实用。例如,如果连 接产生视 图, 而 我们只 按照属于 模式R的属性 对视 图进行 查询, 那 么直观 上我 们希望看到 t 出现在查询结果中。但实际上, 通过视图 R S, t 变得不可见 了, 所以同 样的基于R的查询可能会产生出不同于基于RS的查询的结果。外部连 接不同于通常的 ( 或称为“内部的 ”) 连接, 它在结 果中加 上了每个 关系中并 没有和另一个关系中至少一个元组相连的任何元组。回顾一下例 4. 6, 那些未能与另一关系中任何元组相连的元组称为“悬浮元组”。由于连接的关系中的元组必须具有两个关系的所有属 性, 因 此对每个 悬浮元 组都要用NULL填充只属 于另一个 关系的 属性, 然后再 把悬浮元组加入到连接的结果关系中。例 5. 50 假设我们希望连接两个关系MovieStar ( name, address, gender , birthdate )MovieExec(name,address,cert# ,netWorth)·222·
但要保留那些是影星而不是行政长官或者是行政长官而不是影星的人。我们可以对两个关系执行SQL2 中所谓的“自然完全外部连接”(natural full outerjoin) 。其句法并不出乎意外:MovieSt ar NAT URAL FULL OUTER JOIN MovieExec;该运算的结果是与例 5. 49 具有相同的 6 属性模式的关系。该关系的元组有 3 种。描述同时是影星和行政长官的元组具有所有 6 个非 NULL 属 性。这些也是例 5. 49 结果中 的那些元组。第 二种 元组 描述 的 是非 行政 长官 的影 星。这 些元 组在 取 自MovieStar元 组的 属 性name,address,gender和birt hdate上 有 对 应 值, 而 只 属 于MovieExec, 称 作cert# 和netWorth的属性其对应值为NULL。第 三 种 元 组 描 述 非 影 星 的 行 政 长 官。 这 些 元 组 在 取 自 MovieExec 元 组 的 属 性MovieExec 上 有 对 应 值, 而 只 来 自 MovieStar 的 gender 和 birthdate 属 性 其 对 应 值 为NULL。例如, 图 5. 20 中所示的结果关系的三个元组分别对应于三种人。 □name address gender birthdate cert#networthMary T yler Moore Maple St.'F'9 / 9/ 99 12345 z$100 …Tom Hanks Cherry Ln.'M'8 / 8/ 88NU LL NULLGeor ge Lucas Oak Rd. NU LL NULL 23456 z$200 …图 5. 20 MovieStar和MovieExec的外部连接中的三个元组SQL2 中有许多外部连接的变体可以利用。首先, 除完全外部连接—— 其中两个关系的悬浮元组都用空值填充—— 以外, 我们可以得到左侧( left) 外部连接, 其中 只有左侧( 第一个) 关系中的悬浮元组用NULL填充并包含在结果中。例如,MovieStar NAT URAL LEF T OUTER JOIN MovieExec;将生成图 5. 20 中的前两个元组而不生成第三个。与此类似, 右侧(right) 外部 连 接只 填充 和包 含来 自 右侧 ( 第 二 个) 关系 的 悬浮 元组。因此,MovieStar NAT URAL RIGHT OUT ER JOIN MovieExec;将生成图 5. 20 中的第一个和第三个元组而不生成第二个。外部连接的第二种变体是我们如何指定匹配元组必须满足的条件。我们可以在连接后面加上ON以及匹配元组必须遵循的条件, 而不用关键字NAT URAL。如果我们 还指定 FULL OU TER JOIN, 那么 在匹配了来自 于两个连 接关系的 元组之 后, 还 要为每个 关系中的悬浮元组填充空值并把填充后的元组包含在结果之中。例 5. 51 让我 们重新研究例 5. 47, 在那 里我们 用如下条 件将关 系 Movie 和 StarsIn连接起 来, 条 件就是两 个关系 中的title和movieT it le属性 一致, 而且两 个关 系中的year和movieYear属性也一致。如果我们修改该例进行完全外部连接:Movie FULL OUT ER JOIN StarsIn ONtitle=movieT itle AND year=movieYear;·322·
那么我 们将 不但得 到 StarsIn 中至少 提到 一个 影星的 电影 元组, 而且 得到 没有列 出影 星的电影元组, 对应于属性movieT it le,movieYear和st arName则用NU LL填充。同样, 对于关系 Movie 所列出的任何电影中 都没有出现的影星, 我们将 得到 Movie 中 6 个属 性的对应值均为NULL的元组。 □在例 5. 51 提到 的这 类外部 连接 中, 关键 字 FULL 可 以用 LEFT 或者 RIGHT 来 代替。例如,Movie LEFT OUT ER JOIN StarsIn ON title=movieT itle AND year=movieYear;将给我们至少列出一个影星的 Movie 元组和没有列出影星而填上 NU LL 的 Movie 元组,但是不包括没有列出电影的影星。反过来,Movie RIGHT OUT ER JOIN StarsIn ON title=movieT itle AND year=movieYear;将略掉没有列出影星的电影元组, 但是将包括没有列 出任何电影而用 NU LL 来填充 的影星元组。5. 9. 6 本节练 习练习 5. 9. 1: 对于我们不断滚动的电影数据库模式中的关系St arsIn(movieT itle,movieYear,starName)MovieStar ( name, address, gender , birthdate)MovieExec(name,address,cert# ,netWorth)St udio( name, address, presC# )描述将在下列SQL表达式中出现的元组:( a) Studio CROSS JOIN MovieExec;(b)StarsIn FULL NAT URAL OUT ER JOIN MovieStar;( c) St arsIn F ULL OUT ER JOIN MovieStar ON name = starName;* ! 练习 5. 9. 2: 应用数据库模式Product ( maker, model, type)PC(model,speed,ram,hd,cd,price)Laptop (model, speed, ram, hd, screen, price)Print er(model,color,type,price)写出 SQL 查询, 该查询将生成关于所有产品 —— PC 机、便携式电脑以及打印机 —— 的信息, 包括其生产厂 商( 如果 可以得到) 以及与该产品 有关的 一切信息 ( 就 是说, 在该类产 品的关系中寻找) 。练习 5. 9. 3: 用练习 4. 1. 3 的数据库模式中的两个关系Classes ( class, type, country, numGuns, bore, displacement)Ships(name,class,launched)写出 SQL 查询, 该查询将生成所有可得到的舰艇信息, 包括可从关系 Classes 中得到的信息。如果在Ships中没有提到关于某一等级的舰艇, 就不必生成该等级的有关信息。·422·
锁的粒度不同的DBMS可能对不同种类的数据项加锁。例如, 可以对关系中的单个元组加锁, 也可以对单个磁盘块加锁, 甚至对整个关系加锁。加锁的单元越大, 一个事务必须等待另一个事务的可能性就越大, 即使它们实际上并不访问同一数据。而加锁的单元越小, 加锁机制就越大, 越复杂。· 一致性( consistency) 。数据库通常 都有“一 致状态”的 概念, 即数 据符合我们的 所有期望。例如, 对航班数据库而言,“一个座位不能分配给两个不同的顾客”就是一种适当的一致性条件。在事务处理过程中的某个时刻, 由于旅客之间调换座位, 可能会违背 这种一 致性条件, 但事 务结束 后, 事 务管理程 序必须 保证数据 库满足 所有约定的一致性条件。· 隔离性( isolation) 。当两个或更多的事务并发运行时, 它们的作用效果必须互相分开。也就是说, 我们看到的两个事务并发 运行的效果必须同 两个事务一前一 后运行时的效果完全一样。例如, 如果两个机票代理正在出售同一航班的座位, 而座位只剩下一个了, 那么只能答应一个代理的请求, 而拒绝另一个。如果由于并发操作导致同一座位卖了两次或根本没卖, 都是不能接受的。· 持久性(durabilit y) 。如果事务已经完成, 即使系统出现故障, 甚至事务刚刚完成,就出现了系统故障, 事务的结果也不能丢失。如何实现事务, 使其具有ACID特性, 这本身就能构成一本书的内容。我们就不 准备在这里讨论这个问题了。但 7. 2 节将会讨论在 SQL 语言中, 如何指定属于一个事务 的操作, 以及SQL程序员能够期望从成组的操作到事务的转换中得到什么保证。我们在 本节中还将提纲挈领地介绍一些实现ACID特性的常用技术。加锁造成事务间不 独立的主要原因 是两个或 多个事务 同时读写 数据库 中的同一 数据项。例如, 两个事务企图同时 对同一帐户的结 余进行 修改, 后一个写 操作将 会覆盖前 一个, 那么前一个写操作的结果就丢失了。因此, 大多数DBMS的事务管理 程序能够对事务 要访问的数据项加锁。一个事务对某数据项加锁后, 其他的事务就不能访问它了。例如, 第一个事务锁定帐户 12345 的结余, 于是在另一个事务获准访问它之前, 第一个事务既能对它进行读操作, 也能写入新值。第二个事务将读出新的结余, 而不是旧的。这两个事务间就不会有不良的影响了。日志事务管理程序记录了一个日志文件, 包括每个事务的开始、每个事务所引起的数据库的更新和每个事务的结束。日志总是记在非易失性存储器上, 像磁盘这样的存储介质, 掉电后数据仍完好保存。因此, 虽然事务本身的工作区可能使用易失性的主存, 而日志却总是直接写到磁盘。记录所有的操作是保证持久性的重要手段。·9·
! 练习 5. 9. 4: 重复练习 5. 9. 3, 但是对于没有在 Ships 中提到的 任一等级 C, 当舰艇 的名称和等级同样为C时, 该舰艇的信息也要包含在结果当中。! 练习 5. 9. 5: 在例 5. 46 中我们讨论了查询SELECT*FROM MovieWHERE length< = 120OR length> 120;当电 影长度为 NULL 时, 其 执行情况 很不直 观。找一个更 简单的等 价的查 询, 该 查询 在WHERE子句中只有单一条件( 在条件中没有AND或OR) 。! 练习 5. 9. 6: 我们 在 本 节 学到 的 连 接 运 算符 是 冗 余 的, 在 这 个 意义 上, 它们 总 可 以 用select-from-where表达式来代替。说明如何用select-from-where写出下列表达式:* ( a) R CROSS JOIN S;(b)R NAT URAL JOIN S;( c) R JOIN S ON C; 其中 C 是 SQL 条件。! ! 练习 5. 9. 7: 外部连 接运算符 也可以 用涉及 到其他SQL运 算符 的SQL查询来 代替。不显式地使用连接或外部连接运算符, 说明如何重写下列语句:(a)R NAT URAL LEFT OUT ER JOIN S;( b) R NAT URAL FU LL OUT ER JOIN S;(c)R FULL OU TER JOIN S ON C; 其中C是SQL条件。5. 10 SQL3 中的递归在这一节, 我们将把重点放在SQL3 的一个特性 —— 递归查询上。这个特性刚刚在商业系统中崭露头角。相比之下, 本章前面各节以存在于 SQL2 中的特性为基础, 或者 以能在几乎所有的商业系统中找到的类似特性为基础。此外, 当SQL2 标准被正式采纳时, 本节描述的递归 查询还是基于 SQL3 标 准的一 个草案, 而且可 能在该 草案的基 础上有所 发展。SQL3 实现 递归 的方法 以 4. 4 节 描述 的递 归 Datalog 规 则为 基础, 但 是 也有 一些 修改。首先,SQL3 标准建议, 只有线性递归, 也就是说, 最多有一个递归子目标的规则, 才是必须遵循的; 其次, 层次化的需求, 如我们在 4. 4. 4 节针对否定运算符所讨论的那样, 同样适用于SQL中能引起类似问题的其他运算符, 如聚合。5. 10. 1 在 SQL3 中 定义 IDB 关 系回忆 一 下 4. 4 节 的 内 容, 有 助 于 我 们 区 别 作 为 存 储 表 的 外 延 数 据 库 ( EDB,Ext ensional Dat aBase) 关 系 和 用Datalog规 则 定 义 的 内 涵 数 据 库 (IDB,IntensionalDataBase) 关系。在 SQL3 中, 我 们可 以 通过 由关 键字 WIT H 引 导的 语句 来 定义 等价 的IDB关系。然后可以在WIT H语句内部使用这些定义。WIT H语句的简单格式如下:WITH R AS〈R 的定义〉〈涉及到 R 的查询〉这就是说, 可以定义一 个名称为R的 临时关系, 然后在某个查询 中使用R。更一 般地, 在·522·
WIT H 之后可以 定义几个关系, 用逗号将它 们的定义分 开。这些定 义中的任 何一个都 可以是递归的。定义的几个关系可以相互递归, 这就是说, 每个关系都可以根据某个其他关系( 包 括 它 本 身 在 内 ) 来 定 义。 但 是, 涉 及 到 递 归 的 任 何 关 系 前 都 必 须 加 上 关 键 字RECURSIVE( 递归) 。这样,WIT H语句的格式就是:1. 关键字 WIT H。2. 一个或多个定义。定义之间用逗号分开, 每个定义包括:( a) 一个任选的关键字 RECU RSIVE , 要定义的关系是递归的则需要它。(b) 要定义的关系的名称。( c) 关键字 AS。(d) 定义该关系的查询语句。3. 一个查询, 可以引用前面的任何定义, 并形成该 WIT H 语句的结果。注意到 这一点是 很重要 的: 和 关系的其 他定义 不同, 在WITH语 句中 定义的 关系 只能用在语句内部, 在其他地方不能使用。如果希望得到一个持久的关系, 则应该在 WIT H语句之外, 在数据库的模式中定义关系。例 5. 52 让我们重新考虑一下 4. 4 节作为实例的航空公司航班信息。关于航班的数据存储于关系Flights(airline,frm,to,departs,arrives) 中。①实例中的实际数据在图 4. 19中给出, 现在我们把它重新复制成图 5. 21。图 5. 21 航空公司航班( 重复图 4. 19)在例 4. 37 中, 我 们利用图 5. 21 中所表 示的航 班计算能 从第一 个城市飞 到第二个 城市这样的城市对的集合。在那个例子中, 我们利用这样的两个规则:1. Reaches ( x, y) ← Flights ( a, x, y, d, r )2.Reaches(x,y) ←Reaches(x,z)AND Reaches(z,y) 计算出能把所需信息提供给我们的 IDB 关系 Reaches。从这两个规则中, 我们可以开发出同样意义的 SQL 的 Reaches 定义。该 SQL 查询成为WIT H语句中 的Reaches定 义, 而 我们 用 所需 要的 查询 来结 束 该WIT H语 句。在 例4. 37 中, 结果是 整个Reaches关 系, 但是我 们可以对Reaches关系进行 某种查询, 比如 从丹佛(Denver) 可以到达的城市的集合。·622·①由 于在 SQ L 中 from 是关 键字, 所以 我们 把第二 个属 性名改 为 frm。
相互递归可以用图论的方法检查两个关系或者谓词是否是相互递归。构造一个依赖图, 节点对应于关系( 如果是使用 Datalog 规则则对应于谓词) 。如果关系 B 的定义直接依赖于关系A的定义, 则画一条从A指向B的弧。也就是说, 如果使用Datalog规则, 则A将连同前面的 B 一起出现在规则体中。在 SQL 中, A 将出现在 B 的定义中, 一般出现在FROM子句中, 但也可能作为一项出现在并、交或差中。如果存在一个包含节点 R 和 S 的环, 那么 R 和 S 就是相互递归的。最常见的情况是一个从R到R的循环, 表明R递归地依赖于它本身。请注意, 依赖图类似于我们在 4. 4. 4 节定义分层求反时引入的图。但是, 在那里我们必须区别肯定和否定依赖, 而这里不需要做这种区别。 ?1) WITHRECURSIVE Reaches(frm,to)AS2) (SELECT frm,to FROM Flights)3) U NION4) ( JSELECT R1.frm,R2.to5) FROM Reaches AS R1, R eaches AS R26)WHERE R1.to=R2.fr m)7) SELECT * FROM Reaches;图 5. 22 对可到达城市对的 SQL3 查询图 5. 22 给出了如何用SQL3 的查询来计算Reaches。①1) 行引入了Reaches的定义,该关系实际的定义在 2) 到 6) 行给出。该定义由两个查询的并集组成, 对应于例 4. 37 中定义Reaches的两个规则。2) 行是并集的第一项, 对应于第一 个规则, 也 就是基本规则。它 表明,Flights关系中每个元 组的第二和第三分量( frm 和 to 分量) 构成 Reaches 的一个元组。4) 到 6) 行 对 应 于Reaches定 义 中 第 二 个 规 则, 也 就 是 归 纳 规 则。FROM子 句 中Reaches 的两个别名 R1 和 R2 代表规则中的两个 Reaches 子目标。R1 的第一个分量对应于规则 2 中的 x, R2 的第二个分量对应 于 y。R1 的第二个 分量和 R2 的第一个分量 共同代表变量z; 注意, 在 6) 行这两个分量相等。最后, 7) 行描述了由整个查询生成的关系。它是 Reaches 关系的一个副本。作为替代,我们可以用更复杂的查询来代替 7) 行。比如:7) SELECT to FROM ReachesWHERE frm=' DEN';将产生所有从丹佛可以到达的城市。 □·722·①有 一 个技 术 细节 , 那就 是SQ L3 标 准 只要 求 线性 递 归,DBMS所支 持 的定 义 递归 关 系的 查 询 要 求在FROM子句 中递 归关系 只能 出现一 次。图 5. 22 的 5) 行两次 用到 递归关 系Reach es。虽 然, 该查 询极力 模仿 例 4. 37 也许 是最自然的 方式 。但是 这样写 SQL 3 DBMS 也许支 持, 也许 不支持 。
5. 10. 2 线性递归就像我 们随同例 5. 52 一起 提到的 那样, 解 答中存 在一 个技术 上的 缺点, 因为 SQL3标准只要求SQL3 的实现支持线性递归。形式上, 如果在任何要定义的关系的FROM子句中, 和该关系相互递归的关系最多只出现一次, 那么递归就是线性的。最常见的情况是,要定义的关系 本身在FROM子句中出现 一次, 但也可 能是, 出现一 次的递归 关系是与 要定义的关系相互递归的其他某个关系。例 5. 53 有 趣 的 是, 我 们 可 以 简 单 地 用Flights取 代 图 5. 22 中 5) 行 的 任 一 个Reaches来修改图 5. 22 中的代码。这将使递归具有 4. 4. 3 节的方框中讨论的左递归或右递归的形式。另一种方法 是在WIT H语句中定义一 个附加的关系Pairs, 代表Flights在frm和to分量上的投影, 并且在并集的两个部分都使用该关系。对图 5. 22 的重新编码如图 5. 23 所示。在这里, 我们选择了右递归定义, 虽然我们也可以交换 7) 行的Pairs和Reaches的次序, 从而使定义为左递归。 ?1) WITH2)Pairs AS SELECT frm,to FROM Flights,3) RECU RSIV E Reaches( frm, to) AS4) Pairs5) U NION6) ( SELECT Pairs.fr m,Reaches.to7)FROM Pairs,Reaches8)WHERE Pairs.to=Reaches.fr m)9) SELECT*FROM Reaches;图 5. 23 可到达城市对的线性递归查询请注意, 4. 4. 2 节概 述的迭 代固定 点计算 也能 用于 像这样 的SQL查询, 就像 把它 用于 Datalog 规则一样。因为递归是线性的, Reaches 中的事实可能以某种不同的顺序找到,但所有像(x,y) 这样的能从x飞到y的城市对, 最终都会找到。用我们的采样数据考虑一下第一次循环。因为 Reaches 初始化为空, 只有并集的第一项—— 4) 行的Pairs—— 构成了由图 5. 21 的地图所表示的基本城市对。第二次循环,Pairs和 Reaches 都提供一段航程, 所以我们得到了两段航程的像( SF, CHI) 这样的城市 对。下一次循环, Reaches 同时提供两段航程, 但是没有更多的城市对要加入进来。一般而言, 在第i次循环, 我们可以得到所 有这样的 城市对(x,y) , 即 由i条弧线所 组成的 从x到y的最短路径。 □5. 10. 3 在 WITH 语句中使用视图视图也可以像表那样在WITH语句中定义。句法上的唯一差别是在关系的定义中使用关键字 VIEW。例 5. 54 在图 5. 23 中, 2) 行的关系Pairs也可以定义成视图。我们仅仅需要将 2) 行修改成:2)VIEW Pairs AS SELECT frm,to FROM Flights·822·
甚至有 很好的 理由将 Pairs 定义 为视图。 如果 我们把 它定 义为真 正的 关系, 那么 当执 行WIT H语 句时, 它 将首先 作为 一个整 体构 造出 来, 而 与从 4) 行开 始用 它来 构造Reaches无关。如果它是一个视图, 那么可以用Flights中的元组的分量来代替它构造Reaches。□5. 10. 4 分层求反并不是任意的SQL查询都能以递归关系定义的形式出现。相反, 对查询必须在 某些方面加以限制。最重要的一个要求就是相互递归关系的否定必须是分层的, 就像在 4. 4. 4节所讨论的那样。在 5. 10. 5 节, 我们将看到层次化的原则如何扩展到SQL中可以找到、而 Datalog 或关系代数中不存在的其他结构, 如聚合。例 5. 55 让我 们重新 看一下例 4. 39, 我 们要求 找出 这样的 城市 对(x,y) , 可 以通 过航空公司 UA, 而不能通过 AA, 从 x 到达 y。我们需要用递归来表达一个航空公司采用不确定的航线进行飞行的想法。但是, 否定的情况是以分层的方式出现的: 在例 4. 39 中, 我们在使用递归计算出两个关系UAreaches和AAreaches以后, 取它们的差集。我们可以采用同样的策略写出 SQL3 的查询, 虽然我们必须像例 5. 53 那样用左线性或右线性的形式来取代例 4. 39 的非线性递归。然而, 为了描述一种不同的处理方法, 我们可以另外递归地定义单一的关系 Reaches( airline, frm, to) , 它的三元组( a, f , t) 表示可以从城市f飞到城市t, 可能经过几段航程但是只乘坐一个航空公司a的班机。我们还将用到一个关系 T riples( airline, frm, to) , 它是 Flights 的三个相关分量的投影。查询如图 5. 24 所示。关系Reaches的 定 义 在 3) 到 9) 行 由 两 项 的 并 集 组 成。基 本 项 是 4) 行 中 的 关 系T riples。归纳项是 6) 到 9) 行的查询, 该查询将对 T riples 和 Reaches 本身进行连接。这两项的结果是将所有的元组(a,f,t) 都放到关系Reaches中, 于是我们 可以经过一段或 几段航程从城市 f 到达城市 t, 但是所有的旅程都乘坐航空公司 a 的班机。10) 到 12) 行是查询本身。10) 行给出通过UA能够到达的城市对, 12) 行给出通过AA能够到达的城市对。查询结果是这两个关系的差集。 □ (1) WITH2) T riples AS SELECT airline, frm, to FROM Flights,3) RECUR >SIV E Reaches ( airline, frm, to) AS4) Triples5)UNION6) (SELECT T riples.airline,Tr iples.frm,Reaches.to7) FROM Triples, Reaches8) WHERE Triples. to = Reaches. frm AND9)Triples.airline=Reaches.airline)10) (SELECT frm,to FROM Reaches WHERE airline='UA')11) EXCEPT12) (SELECT frm,to FROM Reaches WHERE airline='AA') ;图 5. 24 对两个航空公司之一能到达的城市的分层查询·922·
例 5. 56 在图 5. 24 中, 11) 行中 EXCEPT 所表示的否定明显是分层的, 因为只有 3)到 9) 行 的递归 结束后它 才适用。另 一方面, 我们 在例 4. 40 观 察到的 对非 分层求 反的 使用, 将转换成在递归关系的定义中对 EXCEPT 的使用。该实例可直接转换成 SQL3, 如图5. 25 所示。该查询只要求 P 的值, 虽然我们可以查询 Q 或者 P 和 Q 的某个函数。图 5. 25 中, 4) 行和 8) 行 使用的 两个EXCEPT在SQL3 中是 非法 的, 因为在 每种 情况下第二个参数都和要定义的关系为相互递归的关系。于是, 这里所用的否定不是分层求反, 因此是不允许的。事实上, 在SQL3 中没有这个问题的解决方案, 而且也不需要有, 因为图 5. 25 中的递归并不真正地定义关系P和Q的值。 □ 1) WITH m2) mRECURSIVE P(x)AS3) (SELECT*FROM R)4) EXCEPT5) (SELECT*FROM Q) ,6) RECURSIVE Q( x ) AS7) (SELECT*FROM R)8)EXCEPT9) ( SELECT * FROM P )10) SELECT*FROM P;图 5. 25 非分层查询, 在 SQL3 中非法5. 10. 5 SQL3 递归中的未定表达 式我们在 例 5. 56 中已经 看到, 在递归 定义中使 用EXCEPT违背了SQL3 所要求的 分层求反原 则。然而, 还有 另一些不使用EXCEPT的查 询形式也是无法令 人接受 的。①例如, 关系的否定可以用 NOT IN 来表示。因此, 图 5. 25 的 2) 到 6) 行也可以写为:RECU RSIVE P( x) AS SELECT x FROM R WHERE x NOT IN Q这种改写仍保留了非分层的递归, 因此是非法的。另一方面, 在 WHERE 子句中仅仅使用 NOT , 如 NOT x= y( 当然也可以写 成 x< >y) 并不会自动和分层求反的条件相违背。那么, 在SQL3 中可以用哪些种类的SQL查询来定义递归关系? 有什么通用规则?其原则 就是, 对于 合法的 SQL3 递归, 递 归关系 R 的 定义 只能涉 及到 使用相 互递 归关系S(S可以是R本身) , 条件是S的使用是单调的。如果在S中加入任意的元组会在R中加入一个或多个元组, 或者使R保持不变, 但决不能导致删除R中的任何元组, 则S的使用是单调的。这条规则在考虑 4. 4. 2 节描述的最小固定点计算概要时是有意义的。我们从递归定义的IDB关系为空 开始, 然 后依次循环地重 复加入元组。如果在某 一循环中加入的 元组·032·①在 技 术上 , 非线 性 递归 即 使没 有 否定 在SQL3 中 也是 无 法 接 受的 , 尽 管SQ L3 的 文 档 中 约定 非 线 性 递归 将“出 现在 SQ L4 中”。但 是, 在这 里讨论 无意 义的荒 谬的 递归形 式, 总强 于讨论 纯粹 非 SQL 3 标 准的 递归形 式。
会导致我们不得不在下一循环中删掉元组, 那么就会出现振荡的危险, 而固定点计算可能永远不会收敛了。在下例中, 我们将看到某些非单调的、在SQL3 递归中非法的构造。例 5. 57 回 顾 一 下 例 4. 40, 这 是 一 个 非 分 层 求 反 的 例 子, 图 5. 25 是 例 4. 40 中Datalog规则的一个实现。规则允许两个不同的最小固定点。正如我们所预料的, 在图 5.25 中P和Q的定义是非单调的。以 2) 到 5) 行的P的定义为例。P依赖于Q, 与之相互递归, 但向 Q 中加入元组会从 P 中删除元组。看看为什么, 假定 R 包含两个元组( a) 和( b) ,而Q包含元组(a) 和(c) 。那么P= {(b) }。然而, 如果我们将(b) 加入Q, 则P为空。加入一个元组导致删除一个元组, 所以我们得到一个非单调的非法构造。当我们试图通过计算最小固定点对关系 P 和 Q 求值时, 缺乏单调性将直接导致 振荡的状态。①例如, 假定R有两个元组{(a) , (b) }。最初,P和Q均为空。这样, 在第一次循环,图 5. 25 的 3) 到 5) 行计算出 P 值为{( a) , ( b) }, 由于 9) 行用的是 P 原有的空值, 因此 7) 到9) 行计算出的 Q 与 P 具有同样的值。现在,R,P和Q的值均为{(a) , (b) }。因此, 在下一次循环中, 3) 到 5) 行和 7) 到 9) 行分别计算出 P 和 Q 均为空。在第三次循环, 二者均取值为{( a) , ( b) }。这个过程将永远延续 下去, 在偶 数次循环两个关 系均为空而在奇 数次循环均为{(a) , (b) }。因此, 从P和Q在图 5. 25 中的“定义”出发我们永远不会得到这两个关系的明确的值。 □例 5. 58 聚合也会导致非单调性, 尽管在 最初这种关系并不 很明显。假定我们 有由下面两个条件定义的一元( 单属性) 关系P和Q:1.P是Q和EDB关系R的并集。2.Q中的一个元组为P的成员的总和。我们 可以 用WIT H语句 表示 这 两个 条件, 不 过 该语 句 和SQL3 的 单调 性 要求 相 违背。图 5. 26 中给出寻找 P 值的查询。 1) WITH2) RECURSIcVE P(x)AS3) (SELECT*FROM R)4)UNION5) ( SELECT * FROM Q)6)RECURSIVE Q(x)AS7) SELECT SU M( x) FROM P8) SELECT*FROM P;图 5. 26 涉及到聚合的非分层查询, 在 SQL3 中非法假设 R 包含元组( 12) 和 ( 34) , 由于 P 和 Q 必 须处于固定点计 算的起点, 因此它们 最初均为空。图 5. 27 汇总了前 6 次循环计算出的值。回忆一下, 我们采用的策略就是, 在一次循环中所 有关系的计算都 使用前一次循环 的值。因此, 第 一次循环的计算 结果P和R相同, 而由于 P 原有的空值用在 7) 行, 结果 Q 为空。·132·①递归 非单 调时, 在WIT H子 句中 对关系 求值 的顺序 会影响 最后 的结果 , 而 递归 单调时 结果 与顺序 无关 。在本例 和下 面的 例子 中, 我们 将 假定 在每 次循 环中 ,P和Q均“并 行 ”求值 。即 每 次循 环 都用 一 个关 系 的旧 值 计算 另 一个关系。
在固定点计算中使用新值人们可 能会问我 们为什么 在例 5. 57 和 5. 58 中用P的旧值 计算Q, 而 不用P的新值。结果将可能依赖于我们在 WIT H 子句中列出的递归谓词定义的顺序。在例 5.57 中,P和Q将收敛于两个可能的固定点之一, 这取决于计算的顺 序。在例 5. 58 中,P 和 Q 仍不会收敛, 事实上它们在每次循环而不是每隔一次循环都会发生变化。循环次数P Q1 `) {( 12 ) , ( 34) } 2 `) {( 12 ) , ( 34) } {( 46 ^) }3 `) {( 12 ) , ( 34) , ( 46) } {( 46 ^) }4 `) {( 12 ) , ( 34) , ( 46) } {( 92 ^) }5 `) {( 12 ) , ( 34) , ( 92) } {( 92 ^) }6 `) {( 12 ) , ( 34) , ( 92) } {( 138 u) }图 5. 27 非单调聚合的固定点迭代计算在第二次循环, 3) 到 5) 行的并是集合R= {( 12) , ( 34) }, 这 也就是新的P值。P的原有值和新值是相同的, 所以在第二次循环 Q= {( 46) }, 46 是 12 与 34 的和。在 第三 次循 环, 我 们通 过 2) 到 5) 行 得到P= {( 12) , ( 34) , ( 46) }。用 原 有 的P值,{( 12) , ( 34) },Q通过 6) 、7) 两行再一次定义为{( 46) }。在 第 四次 循环, P 具 有和 上 次相 同的 值, {( 12) , ( 34) , ( 46) }, 而由 于 12+ 34+ 46=92,Q取值为{( 92) }。注意Q尽管得到了元组( 92) , 但是失去了元组( 46) 。也就是说, 将元组( 46) 加入到P中导致从Q中删除掉一个元组( 碰巧是同一元组) 。这种情形就是SQL3在递归定义中所禁止的非单调性, 可以确认图 5. 26 中的查询是非法的。一般来说, 在第 2i次循环,P包含元组( 12) 、( 34) 和( 46i- 46) , 而Q仅包含元组( 46i) 。 □5. 10. 6 本节练习练习 5. 10. 1: 在 4. 36 节, 我们讨论了关系Sequel0f( movie, sequel)它给出电影直接的续集。我们还定义了一个IDB关 系FollowOn, 其(x,y) 对是这样 的电影, y 或者是 x 的续集, 或者是其续集的续集, 以此类推。(a) 写出作为SQL3 递归式的FollowOn的定义。(b) 8写出递归SQL3 查询, 返回(x,y) 对的集合, 其中电影y是电影x的后集, 但不是续集。(c) ,写出递归SQL3 查询, 返回(x,y) 对的 集合, 其含 义为y是x的 后集, 但既 不是续集也不是续集的续集。! (d) 1写出递归SQL3 查询, 返回电影x的集合, 而x至少有 两个后集。注意两 个后集不能其中一个是续集, 另一个是续集的续集。·232·
! ( e) )写出递归 SQL3 查询, 返回( x, y) 对 的集合, 其中电影 y 是 x 的后集, 而 y 最多有一个后集。练习 5. 10. 2: 在练习 4. 4. 3 中, 我们介绍的关系Rel(class,eclass,mult)描述了一个 ODL 类是如何同其他的类联系在一起的。特别是, 如果存在一个从类 c 到类d的关系, 该 关系具有 元组(c,d,m) 。 如果m=' multi'则该 关系 是多值 的, 而如 果m=' single' 则该关系是单值的。我们在练习 4. 4. 3 还提到可以将 Rel 看作图 的定义, 图 的节点是类, 当且仅当(c,d,m) 为Rel的元组时, 图中将有一条从c到d标记为m的弧。 ( a) 写出生成( c, d) 对的集合 的递归 SQL3 查 询, 从 而使得 上面描述 的图中存 在从c到d的路径。* ( b) 写出生成( c, d ) 对的集合的递归 SQL3 查询, 从而使得存在从 c 到 d 的路径,而每条沿着路径的弧都标记为single。* ! ( c) 写出生成( c, d) 对的集合的递归 SQL3 查询, 从而使得存在从 c 到 d 的路径,而至少有一条沿着路径的弧标记为mult i。 ( d) 写出生成( c, d) 对的集合的递归 SQL3 查询, 从而使得存在从 c 到 d 的路径,但是不存在所有沿着路径的弧都标记为single的路径。! ( e) 写出生成( c, d) 对的集合的递归 SQL3 查询, 从而使得存 在从 c 到 d 的路径,而所有沿着路径的弧交替地标记着single与multi。 ( f) 写 出生成( c, d ) 对 的集合 的递归 SQL3 查 询, 从 而使得 存在 从 c 到 d 的路 径和从d到c的路径, 而每条沿着路径的弧都标记为single。* ! 练习 5. 10. 3: 假设我们用非线性递归来修改 图 5. 23 中的 Reaches 计 算。特别是, 6)到 8) 行可以替换成:6) ( SELECT First. frm, Second. t o7) FROM Reaches AS First,Reaches AS Second8) WHERE First. to = Second. frm)在这里, 为得到新的 对而把元 组变量First和Second表 示的Reaches的两份 副本连接 起来。在固定点计算的第 i 次循环中, 加到 Reaches 的新路径有多长?5. 11 本 章 总 结 SQL: SQL 语言是关系数据库系统的主要查询语言。1997 年对商业系统产生最大影响的标准称为SQL2。该语言的一个正在制订的标准,SQL3, 有望在不久完成。 select-fr om-where 查询: SQL 查 询最常见 的形式 为 select-from-where 形式。它允 许我们得到 几个关系的积(FROM子句) , 把条件 用于结 果的元组 (WHERE子句) 以及生成需要的分量( SELECT 子句) 。 子 查 询: 也 可 以 把select-from-where查 询 作 为 子 查 询 用 于 另 一 个 查 询 的WHERE 子 句中。可以对子查询结 果的关系使用运 算符 EXISTS、IN、ALL 以 及ANY来表示布尔值条件。·332·
设置关系运算: 我们可以分别使用关 键字 UNION、INTERSECT 和 EXCEPT 把关系或者定义关系的查询联系起来从而得到关系的并、交和差。 关系的包模型: SQL 实际上将关系看作是元组的包, 而不 是元组的集合。我 们可以用关键字DIST INCT强制除掉重复 元组, 而在 某些情况下包 并不是默 认的, 这时可用关键字 ALL 令结果为包。 聚合: 出现在关系的某一列的值可以用关键字SUM、AVG( 平均值) 、MIN、MAX或 COUNT 之一来汇总( 聚合) 。元组可以在聚合之前用关键字 GROUP BY 进行分组。某些组可以用关键字HAVING引入的子句略掉。 更新语句: SQL 允许我们改变关系中的元组。我们可以 INSERT ( 插入新元 组) 、DELET E( 删除元组) 或UPDAT E( 修改某些已有的元组) —— 通过写出利用 这三个关键字之一的 SQL 语句来实现。 数据定义:SQL有用来说明数据库模式的各个元素的语句。CREAT E T ABLE语句允许我们说明存储的关系( 称为表) 的模式, 指定其属性 和类型。我们也可 以用CREATE DOMAIN语句来定义 数据类型的名称, 然后该名 称可以用在关系 模式的说明中。这些 CREAT E 语句还允许我们说明属性和域的默认值。 更改模式: 我们可以用ALT ER语句更改数据库模式的外观。这些更改包括在关系 模式 中增 加和 删 除属 性, 以及 更改 与 属性 或域 相关 的默 认 值。我们 还可 以 用DROP语句完全撤消关系、域或其他的模式元素。 索引: 虽然不是 SQL 标准的一部分, 但是商业 SQL 系统允许对属性说明索引; 这些索引加速了某些涉及到索引属性指定值的查询或更新。 视图: 视图的定义说明如何从存储在数据库中的表来构造一个关系( 视图) 。可以像对表一样对视图进 行查询, 而SQL系统将修改 对视图的查询, 从而用对定 义视图的表的查询来代替该查询。 空值:SQL提供了特殊值NULL, 出现在无法得到具 体值的元组分量 中。NULL的算术和逻辑运算具有特殊性。任何值( 甚至另一个 NULL) 和 NULL 进行比较,都得到真值UNKNOWN。反 过来, 在布 尔值表达式中该 真值就好像处于T RUE和 FALSE 之间的中间状态。 连接表达式:SQL有诸如可以应用于关系的NAT URAL JOIN之类的运算符, 或者本身作为查询, 或者在 FROM 子句中定义关系。 外部连接:SQL还 提供了OUTER JOIN运算符来连接 关系, 但是也 在结果中 包括了来 自一个 或两 个关系 中的 悬浮元 组; 在结 果关系 中悬 浮元 组将用 NULL 来填充。 SQL3 递归: SQL3 标准将 包含递归 定义临 时关系并 在查询 中使用这 些关系的 方法。推荐标准要求递归中涉及到的否定和聚合都是分层的; 也就是说, 递归定义的关系不能根据其本身的否定或者聚合来定义。·432·
事务提交为了保证 持久性和原子性, 事务一般 以“试验”方式完 成, 也 就是说, 在试 验过程中 计算对数据库要做的更新, 但并不真正地更新数据库本身。事务即将完成时, 也就是事务提交时, 更新的内容均已复制到日志记录中。该日志记录首先复制到磁盘上。然后才把更新的内容写入数据库本身。即使系统在这 两步中间出现了 故障, 当系统 恢复正常 后, 我 们可以 阅读日志 文件, 看看还需要进行哪些数据库更新操作。如果系统在所有的更新操作写入日志文件之前出现了故障, 我们就重新执行该事务, 确保不会偶尔发生诸如两次预定航班座位或者两次记入借方帐户之类的事情。1. 2. 5 客户程 序-服务程序体 系结构许多种 现代软件 采用于 客户程序-服务 程序体系 结构, 这种体 系结构中, 把一 个进 程( 客户程序) 发出的请求送到另一个进程( 服务程序) 去执行。数据库系统也不例外, 通常将图1. 1中各组成部分的工作分成一个服务进程和一个或多个客户进程。在最简单的客户程序/ 服务程序体系结构中, 除了与用户相互配合并将查询或其他命令传给服务程序的查询接口以外, 整个 DBMS 就是一个服务程序。例如, 关系系统通常用SQL语言来表达从客 户程序到服务程 序的各种请求。然 后数据库服务程 序给出回答, 用表即关系的形式传给客户程序。客户程序和服务程序之间的关系可能会更复杂, 尤其是答案极大的时候。关于这个问题, 我们将在 1. 3. 3 节详细介绍。如果有很多用户同时使用数据库的话, 服务程序就会成为“瓶颈”, 所以也有一种趋势—— 让客户程序做更多的工作。1. 3 未来的数据库系统今天, 数据库的领域有很多发展趋势, 这些趋势将引导这门学科沿着各种各样的新方向发展。其中有些是正在改变传统DBMS特性的新 技术—— 例如面向对 象的编程, 约束和触发, 多媒体数据, 或者万维网( World Wide Web, WWW) 。其他 的发展趋势包括 新的应用, 如数据仓库或信息集成。这一节我们将简单地介绍一下未来数据库系统的几种主要发展趋势。1. 3. 1 类型、类和 对象人们普遍认为 面向对象的编程 是更好的 程序结构 工具, 甚至是 更可靠的 软件实现 工具。面向对象的编程, 首先在 Smallt alk 语言中推广, 随着 C+ + 的发展和许多以前基于 C的软 件开 发向C+ + 移 植, 获得了 很大 的发 展。最近, 适于 在万 维网 上进 行 程序 共享 的JAVA 语言, 也提高了人们对面向对象编程的关注。面向对象的实例对数据库领域也同样有吸引力, 而一些公司正在销售所谓“面向对象”的DBMS。这里我们将会回顾隐藏在“面向对象”之后的概念。·01·
5. 12 本章参考文献可 以通 过全国 科学 技术学 会(NIST, 其 前身 为美 国 国家 标准 局(National Bureau ofStandards) ) 联机获得 SQL2 和 SQL3 标准。这些文档可以通过匿名的 FT P 或 HT TP 来获取。主机名为speckle.ncsl.nist.gov。SQL2 标准和SQL3 标准的当前版本可以在目录isowg3/ dbl/ BASEdocs中找到。若对SQL2 的正式语法特别感兴趣, 则可查阅文件isowg3/ dbl/ BASEdocs/ sql-92. bnf目录isowg3/x3h2 中 包含了许 多解释SQL2 和SQL3 标 准的现 行的和 历史的 文档。这些文档全都具有以 X3H2 开头的报告编号。要想通过HT TP获取SQL文档, 使用URLhttp: / / speckle. ncsl. nist. gov/ ~ftp后面加上一个上面提到的目录的路径。有些书给出更多 SQL 编程细节。我们最感兴趣的是[ 2] , [ 3] 和[ 6] 。SQL最初在[ 4] 中定义。它作 为系统R[ 1]( 第 一代关系数据 库原型之 一) 的一部分 而实现。参考文献[ 5] 是 SQL3 递归的原始资料。[ 1] 'Astrahan, M . M. et al. , System R: a relational approach to data management, ACM Tr ansactionson Database Systems, 1: 2,pp. 97~137, 1976.[ 2] 'Celko,J. ,SQL for Smarties,Mor gan-Kaufmann,San Francisco, 1995.[ 3] 'Date, C. J. and H. Darwen, A Guide to the SQL Standard, Addison -Wesley, Reading, MA, 1993.[ 4] 'Chamberlin,D.D. ,et al. ,SEQUEL2:a unified approach to data definition,manipulation,andcontrol, IBM Journal of Research and Development, 20: 6, pp. 560~575, 1976.[ 5] 'Finkelstein,S.J. ,N.Mattos,I.S.Mumick,and H.Pir ahesh,Expressing recursive queries inSQL, ISO WG3 report X3H2-96-075, March , 1996.[ 6] 'Melton,J.and A.R.Simon,Understanding the New SQL:A Complete Guide,Morgan-Kaufmann,San Fr ancisco, 1993.·532·
第 6 章 S Q L中 的 约 束 和 触 发 程 序 本章将涉及关于建立“主动性”( active) 元素的 SQL 的各个方面。主动性元素是 这样的表达式或语句, 即一旦写出就保存在数据库中, 并希望在适当的时候予以执行。动作的时机可能是某个事件发生的时候, 比如对特定的关系进行插入操作的时候, 也可能是数据库发生变化导致某个布尔值条件为真的时候。编写更新数据库应用程序的人所面对的重要问题之一就是新的信息可能在很多方面都是错误的。数据库更新过程中, 为了保证关系中不出现不适当的元组, 最直接的方法就是编写应用程 序, 从而对 每个插入、删除、修 改命令 都进行与 之相关 的保证正 确性的必 要检查。不幸的是, 正确性检查往往都很复杂, 并且总是重复的; 应用程序必须在每次更新操作之后进行相同的检验。幸运的是,SQL2 将表示完整性约束的各种技术作为数据库模式的一部分提出来。在本章中, 我们将研究其主要方法。 首先是键码约束, 把某个属性或属性集说明为关系的键码。 其次, 我们考虑参照完整性, 即要求某个关系 中的属性或属性 组的值( 如Studio中的presC# ) 还必须作为另一个关系中的属性或属性组的值( 如 MovieExec 中的 cert# ) 出现。然后, 我们会看到对域的一些约束, 包括唯一性(“键码”) , 将域限制为某些特定值, 不允许为NULL 值。接下来, 我们将研究把元组或关系作为整体的约束, 以及关系之间称为“断言”(assert ion) 的约束。每当对相关关系进行更新时, 都要对这些约束进行检验。最后, 我们讨论“触发程序”( trigger) , 它是由 某些 特定 事件( 例如 对特 定的关 系进 行插 入) 启动 的主动 性元 素的 一种形 式。在SQL2 标 准中 没有 出现 触发, 而 在 后续 的标 准SQL3 中, 则 包含了触发。尽管在 本书的写作过程 中 SQL3 并 没有最终完成, 但一些商 用数据库系统为用户提供了某种形式的触发程序。6. 1 SQL 中的键码也许在数据库中最重要的约束类型就是说明某个属性或属性集构成关系的键码。也就是, 禁止关系的两个元组在说明为键码的属性上一致, 或者禁止在共同构成键码的属性集的所有属性上一致。就像其他许多约束一样, 键码约束在SQL的CREAT E T ABLE命令 中 说 明。有 两 种 相 似 的 说 明 键 码 的 方 法: 使用 关 键 字 PRIMARY KEY 或 关 键 字U NIQUE。然而, 表中只能有一个主键码, 但可以有任意数量的“unique”说明。6. 1. 1 说明键 码主键码可以由关系的一个或多个属性组成。在定义存储关系的 CREAT E T ABLE 语·632·
句中有两种方式来说明主键码。1. 可以在关系模式中列出属性时说明某个属性为主键码。2. 可以在模式中说明的项目表( 到目前为止 仅为属性表) 中加入额外 的说明, 说 明某个特定的属性或属性集构成主键码。对于方法 1, 我们在属性及其类型之后加上关键字 P RIMARY KEY。对于方法 2, 我们将在 属性表中 引入新 元素, 该元素由 关键字PRIMARY KEY以及 括号 内的构 成该 键码的属性或属性集组成。注意, 如果键码中有多个属性, 则要求用方法 2。例 6. 1 让 我 们重 新 考 虑 例 5. 32 中 关 系MovieSt ar的 模 式。 该 关 系 的 主 键 码 为name。因此, 我们可以在说明name的行中加入该事实。图 6. 1 是反映这种变化的图 5. 13的修订版。作为选择, 我们也可以用主键码的单独的定义。在图 5. 13 的 5) 行后面加上主键码的说明, 而不必在 2) 行中说明。最终的模式说明如图 6. 2 所示。 □ 1) VCREATE TABLE MovieStar(2)name CHAR( 30)PRIMARY KEY,3)address VARCHAR( 255) ,4) gender CHAR ( 1) ,5) birthdate DATE) ;图 6. 1 使 name 为主键码 1) zCRE 4ATE TABLE MovieStar(2)name CHAR( 30) ,3)addres VARCHAR( 255) ,4)gender CHAR( 1) ,5) birthdate DATE,6) PRIMARY KEY ( name)) ;图 6. 2 单独说明主键码注意, 在例 6. 1 中, 由于主键码是单一属性, 图 6. 1 和图 6. 2 的格式都是可以接受的。但是, 在主键码超过一个属性的情况下, 我们必须用图 6. 2 的格式。例如, 如果我们说明关系Movie的模式, 其键码为属性对tit le和year, 就要在属性表后面加上一行PRIMARY KEY(tit le,year)另一种说明键码的方法是用关键字 UNIQUE 。这个单词恰好可以出现在 PRIMARYKEY 可以出现的地方: 或者跟 在属性及其类型 后面, 或 者作为 CREAT E T ABLE 语 句中一个单独的项 目。它 和 主键 码 的 说 明 具有 同 样 的 含 义。但 是, 表 中 可 以 有任 意 数 量 的U NIQUE说明, 却只能有一个主键码。例 6. 2 图 6. 1 的 2) 行也可以写成 2) name CHAR( 30) UNIQUE,如 果我 们认 为两个 影星 不可 能具 有同 样的 地址 ( 值得 怀疑 的假 设) , 那么 也可 以 将 3) 行改为 3)address VARCHAR( 255)UNIQUE,同样, 如果愿意选择另一种格式, 那么也可以将图 6. 2 中的 6) 行改为 6)U NIQUE(name) □键码约束是既可以用PRIMARY KEY也可以用UNIQUE说明的约束。关于这两种说明之间区别的要点, 可参看 6. 2. 2 节的方框“主键码和唯一值属性”。·732·
6. 1. 2 实施键 码约束回顾我们在 5. 7. 7 节讨 论的索引, 可以 从中了 解到, 尽管索引 并不是 SQL 标准的 一部分, 但是每种SQL实现都有办法把建立索引作为数据库模式定义的一部分。为了 支持指定主键码值的普通类型的查询, 建立基于主键码的索引是很正常的。我们也会想到在说明为UNIQUE的其他属性上建立索引。于是, 当查 询的 WHERE 子句 中包 含了 使键 码等 于特 定值 的 条件 时, 例如 在例 6. 1的MovieSt ar关系的情形下使name=' Audrey Hepburn', 不用查找关系中所有的元组,就可以很快地找到匹配的元组。许多SQL的实 现提供了带关键字UNIQUE的索引建立语 句, 在为属性 建立索引 的同时将属性说明为键码。例如, 语句CREAT E U NIQUE INDEX YearIndex ON Movie(year) ;和 5. 7. 7 节 的例 子中 的建 立索 引 语句 具有 相同 的 效 果, 但是 它 还为 关 系 Movie 的 属 性year说明了唯一性约束( 这不是一个合理的假设) 。让我们研究一下 SQL 系统是如何实施键码约束的。原则上, 每当我们试图改变 数据库时必须对约束进行检验。但是, 必须明确的是, 只有更新关系R时关系R的键码约束才可能发生违背。实际上, R 的删除操作不会导致违背; 只有插入或修改操作才会。因此, 只有对关系进行插入或修改时才检验键码约束, 这对于SQL系统已经成为通用惯例了。SQL 系统 要想 有效 地 实施 键码 约束, 在 说明 为 键码 的属 性上 建 立 索引 是 非常 重 要的。如果能够得到索引, 那么无论向关系中插入元组或修改某个元组的键码属性值, 我们都应利用索引来检验在说明为键码属性上具有相同值的元组是否已经存在。如果存在, 系统就必须阻止该更新的发生。如果在键码属性上没有索引, 原则上依然可以实施键码约束。但是, 系统为查找具有相同键码值的元组就必须搜索整个关系。这个过程是极其费时的, 因此, 对大型关系数据库的更新实际上变得不可行了。6. 1. 3 本节练 习* 练习 6. 1. 1: 在 3. 9 节中我们不断滚动的电影数据库实例为其所有关系定义了键码。修改你在练习 5. 7. 1 中的 SQL 模式说 明, 使 之包括所 有这些 关系的键 码说明。回 顾一下,StarsIn的所有三个属性构成其键码。练习 6. 1. 2: 为练习 4. 1. 1 中的 PC 机数据库的关系列出合适的键码。修改练习 5. 7. 2 中的SQL模式, 使之包括这些键码的说明。练 习 6. 1. 3: 为练 习 4. 1. 3 中 的战 列舰 数 据库 的关 系列 出合 适 的键 码。修 改 你在 练 习5. 7. 3 中的SQL模式, 使之包括这些键码的说明。6. 2 参照完整性和外键码数据库模式的第二种重要的约束类型就是某些属性的值必须有意义。也就是, 如关系·832·
Studio 中的 presC# 属性要 求表示某个特定 的电影行政长官。“参照完整性”约束不言 而喻, 如果某个制片公 司元组在presC# 分量中有确定 的证书号c, 那么c就 不会是假的: 它是某个 实际的电影行政 长官的证书号。按照 数据库的 术语,“实 际”的行政长 官指的是 在MovieExec关系中提到的行政长官。因此, 必然存在某个MovieExec元组, 其cert# 属性值为 c。6. 2. 1 说明外 键码约束在SQL中我们 可以 将一 个关系 的属 性或属 性组 说 明为 外键 码, 它参 照 第二 个关 系( 可以是同一个关系) 的某个( 些) 属性。该说明的含意是双重的:1. 必须说明第二个关系的被参照属性为该关系的主键码。2. 出现 在第一个 关系的 外键码属 性中的任 何值也 必须出现 在第二 个关系的 相应 属性中。也就是存在着连接这两个属性或属性集的参照完整性约束。像对主键码一样, 我们采用两种方法来说明外键码。(a) 如果外 键码是单 一属性, 我们 可以在其 名称和 类型后面 加上“参照”某个 表的 某个属性( 必须为主键码) 的说明。说明的格式为:REFERENCES〈表〉(〈属性〉)( b) 另一种方法是, 在 CREAT E T ABLE 语 句的属性表后面加 上一个或多个表 明属性集为外键码的说明。然后, 我们给出外键码所参 照的表及其属性 ( 必须为 主键码) 。说明的格式为:FOREIGN KEY〈属性〉REFERENCES〈表〉(〈属性〉)例 6. 3 假设我们想要说明关系Studio(name,address,presC# )其主键码为 name, 并且有一个外键码 presC# 参照如下关系的 cert# :MovieExec(name,address,cert# ,netWorth)我们可以直接说明 presC# 参照 cert# , 如下所示:CREAT E T ABLE Studio( name CHAR( 30) PRIMARY KEY, address VARCHAR( 255) , presC# INT REFERENCES MovieExec( cert# )) ;另一种格式是对外键码单独说明, 如CREAT E T ABLE Studio( name CHAR( 30) PRIMARY KEY, address VARCHAR( 255) , presC# INT , FOREIGN KEY presC#REFERENCES MovieExec(cert# )) ;注意, 外键码所参照的属性,MovieExec中的cert# , 实际上是所在关系的主键码。如·932·
主键码和唯一值属性PRIMARY KEY的说明几乎是UNIQUE说明的同义词。最明显的区别就是, 对于表只能有唯一一个主键码, 但是可以有任意数量的 UNIQUE 属性或属性集。然而,还有一些细微的差别。1. 外键码只能参照某个关系的主键码。2. 数 据库管理 系统的实 现有给“主 键码”概念赋 予某种特 定含义 的选项, 但 这并不是 SQL2 标准的一部分。例如, 就像 在 6. 1. 2 节讨论 的那样, 数据库销售商可能总是在说明为主键码的键码上设置索引, 即使该键 码包含不只一个 属性,但 在其他 属性上就 需要用户 显式地 请求建立 索引。另外, 如 果表 有主键 码的话, 可能总是按其主键码排序。果我 们 要 将 presC# 合 法地 说 明为 Studio 的 外 键 码, 而 所 要 参 照的 是 MovieExec 中 的cert# , 那么就需要将cert# 说明为MovieExec的主键码。这两种 外键码说 明的含 义为, 无论何时 只要有 某个值出 现在Studio元组 的presC#分量中, 该值也必须出现在某个 MovieExec 元组的 cert# 分量中。一个例外就是, 即使某个特定的Studio元组将NULL作为其presC# 分量的值, 也不必将NULL作为cert# 分量的 值( 实际 上, 无论如 何也 不 允许 在主 键码 属性 中 出现 NULL 值, 这听 起 来更 现实 一些; 参见6. 3. 1 节) 。 □6. 2. 2 保持参 照完整性我们 已经 看 到如 何 说 明外 键 码, 并 且也 了 解到 这 种说 明 意味 着 外 键码 中 的任 何 非NULL值也必须出 现在被参照关系的 相应属性中。但是, 当面临对 数据库的 更新时如 何保持这种约束呢? 数据库实现者可以在三个可选方案中选择。默认策略: 拒绝违法更新SQL 的默认策略 就是任何与参照 完整性约束相违 背的更新均为系 统所拒绝。例如,考虑例 6. 3, 要求关系St udio中的presC# 值也是MovieExec中的cert# 值。如下的动作将为系统所拒绝( 也就是产生运行时错误) 。1. 我 们尝试 插入 一个新 的St udio元 组, 其presC# 值不 是NULL并 且 也不 是任 何MovieExec 元组的 cert# 分量。该插入将为系统所拒绝, 该元组决不会插入到 St udio 中。2. 我们尝试修改一个Studio元组, 将其presC# 分量改为不是任何MovieExec元组cert# 分量的非NULL值。该修改将遭到拒绝, 该元组不会改变。3. 我们尝试删 除一个 MovieExec 元组, 其 cert# 分量 作为 presC# 分量 出现在一 个或多个Studio元组中。该删除将遭到拒绝, 该元组将保留在MovieExec之中。4. 我们尝试通过改变 cert# 的值来修改一个 MovieExec 元组, 而原有的 cert# 是某个电影制片公司的presC# 值。所要做的修改将再次遭到拒绝,MovieExec也保持原样。·042·
级联策略要对 MovieExec 这样 的被参照关系进 行删除或修改( 也就是上述 的第三和第四类 更新) 还有另一种处理方法, 称为级联策略(cascade policy) 。在这种策略下, 当我们删除对应于某制 片公司总 裁的MovieExec元组时, 为了保 持参照 完整性, 系统必 须从St udio中 删除参照的元组。修改也以类似方式处理。如果我们将某个电影行政长官的 cert# 从 c1 改为c2, 而存在其presC# 分量值为c1 的某个Studio元组, 那么系统也将该presC# 分量值改为c2。置空策略还有一 种处理方 法, 就 是将 presC# 的值 从所删 除或 修改 的制片 公司 总裁的 对应 值改为 NULL; 这种策略称为置空( set-null) 。这些选项都可以单独地用于删除和修改, 这些选项是和外键码的说明一起描述的。可以通过在ON DELET E或ON UPDAT E后面 加上所选 择的SET NULL或CASCADE来说明相应的选项。例 6. 4 让我们看看如何修改例 6. 3 中的如下关系的说明Studio(name,address,presC# )来规定对如下关系的删除和修改操作MovieExec(name,address,cert# ,netWorth)图 6. 3 采 用 了 例 6. 3 中 的 第 一 个 CREATE T ABLE 语 句, 并 用 ON DELET E 和 ONU PDAT E 子句对其 扩展。5) 行指出当我 们删除 MovieExec 元组时, 将身为 总裁的他( 或她) 所在的制片公司的presC# 置为NULL。6) 行指出, 如果我们修改MovieExec元组的cert# 分量, 那么,St udio中其presC# 分量具有相同值的任何元组将进行同样的修改。注意, 在本例中, 置空策略对删除 操作更 有意义, 而级联 策略看 起来对修 改操作更 为可取。例如, 如果某制片公司总裁退休, 我们会期望该制片公司将暂时“没有”总裁而存在。但是, 对制片公司总裁证书号的修改很可能是一种事务性的变化。人继续存在而且仍是制片公司总裁, 所以我们希望 Studio 中的 presC# 属性值将随之变化。 □ ?1) CREATE TABLE Studio(2) name CHAR ( 30) PRIMARY KEY,3)address VARCHAR( 255) ,4)pr esC#INT REFERENCES MovieExec(cert# )5) ON DELET E SET NULL6)ON UPDATE CASCADE ) ;图 6. 3 选择保持参照完整性的策略6. 2. 3 本节练 习练习 6. 2. 1: 针对电影数据库·142·
悬浮元组和更新策略外键码值并未出现在被参照关系中的元组称为悬浮元组。回顾一下, 未能参与到连接中的元组也称为“悬浮”的。这两个概念密切相关。如果元组的外键码值在被参照关系中遗漏了, 那么, 该元组就不会参与它所在的关系和被参照关系的连接运算。悬浮元组恰好是针对外键码约束而与参照完整性相违背的那些元组。· 默认策略就是, 当且仅当在参照关系中 构造出一个或多 个悬浮元组时, 对于被参照关系的删除和修改操作将予以禁止。· 级联策略就是, 删除 或 修 改( 分 别取 决 于对 被 参 照关 系 的更 新 是删 除 还 是修改) 所有构造出来的悬浮元组。· 置空策略就是, 把每个悬浮元组中的外键码值都设置为NULL。Movie(title,year,length,inColor,studioName,producerC# )StarsIn( movieT itle, movieYear , starName)MovieSt ar(name,address,gender,birthdate)MovieExec( name, address, cert# , netWorth)Studio(name,address,presC# )说明下列参照完整性约束:(a) 电影制片人(producerC# ) 必须是MovieExec中提到的某个人。与该约束相违背的对MovieExec的更新将予以拒绝。( b) 重复( a) , 但是违例将导致把 Movie 中的 producerC# 置空。(c) 重复(a) , 但是违例将导致删除或修改相违背的Movie元组。( d) 出现在 StarsIn 中的电影也必须出现在 Movie 中。通过拒绝更新来处理违例。( e) 出现在 St arsIn 中的影星也必须出现在 MovieStar 中。通过删除相违背的元组来处理违例。* ! 练习 6. 2. 2: 我 们 希 望 说 明 这 样 的 约 束: 在关 系 Movie 中 的 每 部 电 影 都 必 须 伴 随StarsIn中的至少一名影星出现。我们能通过外键码约束来实现吗?为什么行, 或为什么不行?练习 6. 2. 3: 基于练习 4. 1. 3 中的数据库模式Classes(class,type,country,numGuns,bore,displacement)Ships(name,class,launched)Battles(name,date)Outcomes(ship,batt le,result)写出下列参照完整性约束。对键码进行合理的假设并且通过设置参照的属性值为空来处理所有的违例。* ( a) Ships 中提到的每个等级都必须在 Classes 中提到。( b) Outcomes 中提到的每个战役都必须在 Battles 中提到。(c)Outcomes中提到的每艘舰艇都必须在Ships中提到。·242·
6. 3 对属性值的约束我们已经看到了键码约束, 它强制某些属性在关系的所有元组中具有截然不同的值, 我们还看到了外键码约束, 它强制两个关系的属性之间保持参照完整性。现在, 我们将看到第三种重要的约束: 它限制了某些属性分量中出现的值。这些约束可以由如下两者之一来表示:1. 在关系模式的定义中对属性的约束, 或者2. 对域的约束, 随后把域说明为上述属性的域。在 6. 3. 1 节我们将引入一类简单的对属性值的约束: 属性值不为 NULL 的约 束。然后, 在 6. 3. 2 节我们 将涉及 到第一类 约束的 主要 形式: 基于 属性 的CHECK( 检 验) 约束。第二类( 对于域) 的约束在 6. 3. 3 节讨论。我们将在 6. 4 节看到其他更通用的约束类型。这些约束如同对 单一属性值的约 束一样, 能 够用来限 制整个元 组甚至 整个关系 或几个关 系的变化。6. 3. 1 非空约 束和某个属性 相关的一类简单 约束就是NOT NULL。其效果就是不接受 该属性为 空的元组。该约束通过在 CREAT E T ABLE 语句中属性说明后面的关键字 NOT NULL 来说明。例 6. 5 假设关系 Studio 要求 presC# 不为 NULL, 可以通过将图 6. 3 的 4) 行改为4)presC#INT REFERENCES MovieExec(cert# )NOT NULL来实现。这种改变导致几种后果。例如:· 我们不能将元组的presC# 分量的值修改为NULL。· 我 们不能 仅指 定名称 和地 址就 把元组 插入 到 Studio 中, 这是 由于 插 入的 元组 在presC# 分量上为NULL。· 我们不能像图 6. 3 的 5) 行的情况那样采用置空策略, 5) 行的情况告诉系统通过使presC# 为NULL来改正外键码的违例。 □6. 3. 2 基于属 性的 CH ECK 约束更复 杂的约束 可以在 属性说明 中附加 上关 键字 CHECK( 检验) , 随后 加上括 号内 的条件, 该属性的每个值必须满足该条件。实际上, 基于属性的CHECK约束就像是对值( 比如, 枚举的合法值或者算术不等式) 的简单限制。然而, 原则上, 条件可以是 SQL 查询中跟在WHERE后面的任何内容。该条件可能引用所约束的属性。但是, 如果它引用其他任何关系或关系的属性, 那么, 必须在子查询的 F ROM 子句中引入该关系( 即使所引用的关系是所检验的属性所在的关系) 。每当任何元组得到某个属性新的值时, 就对基于该属性的 CHECK 约束进行检验。可以通过对元组的修改而引入新值, 或者把新值作为插入元组的一部分。如果新值与约束相违背, 那么就拒绝更新。如果数据库更新并没有改变同约束相关的属性值, 而且这种限制会导致与约束相违背, 那么就不必 检验基于该属性 的CHECK约束, 如 同我们将在例 6. 7·342·
中看到的那样。首先, 让我们考虑一个基于属性检验的简单例子。例 6. 6 假设我们要求证书号至少为 6 位。我们可以将图 6. 3 的关系Studio( name, address, presC# )的模式说明的 4) 行修改为4) pres C# INT REFERENCES MovieExec( cert# )CHECK(presC# > = 100000)对于另一个例子, 关系MovieStar(name,address,gender,birthdat e)的属性 gender 在图 5. 13 中说明为数据类型 CHAR( 1) , 即单个字符。然而, 实际上我们期望出现在那里的字符仅为' F'和' M'。用下面内容来替代图 5. 13 的 4) 行即可。4) gender CHAR( 1) CHECK ( gender IN ( ' F' , ' M' ) ) ,上述条件用到一个显式的二值关系, 并表明任何gender分量的值必须在此集合之中。 □允许在要检验的条件中提到该关系的其他属性或元组, 甚至提到其他关系, 但要这样做, 就需要在条件中有子查询。正像我们所说的那样, 条件可以是select-from-where这样的 SQL 语句中 跟在 WHERE 后 面的任何 内容。但是, 约 束的 检验只 和所 讨论的 属性 相关, 而不是和约束中提到的每个关系或属性都相关。因此, 如果要检验的属性之外的某些元素发生变化, 则条件可以为假。例 6. 7 假设我们可以通过一个 要求所参照的值存 在的基于属性的CHECK约 束来模拟参照完整性约束。下面是模拟这种要求的错误尝试, 其要求是, 关系Studio(name,address,presC# )的元组中的 presC# 值, 必须出现在关系MovieExec(name,address,cert# ,netWorth)的某个元组的 cert # 分量中, 假设把图 6. 3 的 4) 行替换成 4)presC#INjT CHECK( presC# IN ( SELECT cert # F ROM MovieExec) )该语句是合法的基于属性的CHECK约束, 但让我们来看一看其效果。· 倘若我们试图向 Studio 中插入一个 新元组, 而该元组的 presC# 值不是任何 电影行政长官的证书号, 则插入被拒绝。· 倘若我们试图修改某个 St udio 元组 的 presC# 分 量, 而新值 并不是电影行政 长官的cert# , 则修改被拒绝。· 然而, 如果我们改变 MovieExec 关系, 比如删除某制片公司总裁的元组, 这种改变对 于上述的CHECK约束却是不可见的。因此, 即使现在违背了presC# 上基于属性的 CHECK 约束, 该删除操作也是允许的。我们将在 6. 4. 2 节看到如何用更强的约束形式正确地表述该条件。 □6. 3. 3 域约束我们也可以通过说明带有类似约束的域( 见 5. 7. 6 节) 并说明该域为属性的数据类型来约束属性值。唯一重要的区别就是当我们试图描述对域值的约束时, 没有该值的称谓。·442·
类型系统面向对象的编程语言给用户提供了丰富的类型聚集。从基本类型, 通常是整型、实型、布尔型和字符串开始, 可以用类型构造符构造新的数据类型。类型构造符一般允许我们构造:( 1) 记录 结 构 (record structure) 。 给 出 类 型 表T1 ,T2 , …,Tn 和 对 应 的 域 名 表 ( 在Smalltalk 中称 为实例 变量, 即 instance variable) f 1 , f 2 , …, f n , 就 可以构 造一个包 含 n 个成分的记录类型。第i个成分的类型是Ti, 用它的域名fi 来引用。记录结构其实就是C或C+ + 中的“结构”( struct) 。( 2) 聚集类型(collection type) 。给定一个类型T, 就可以用聚集运算符将其构造成新的类型。不同的语言使用不同的聚集运算符, 但有一些是通用的, 包括数组、列表和集合。因 此, 如 果T是 基 本类 型 整 型, 我 们 可 以 构 造 整 型 数 组、整 型 列 表 或 整 型 集 合 等 聚 集类型。( 3) 引用类型(reference t ype) 。类型T的引用是这样一种类型, 它的值用来指向类型T 的值。在 C 或 C+ + 中, 引用是指向某个值的“指针”, 也就是说, 引用是个单元, 其中存放着该值所在的虚拟存储器的地址。指针模型常用于帮助理解引用。然而, 在数据库系统中, 由于数据存储在很 多磁盘上, 或许分布 在许多主 机上, 引用必 然比指针 复杂得 多。比如, 引用可能包括引用的值所在的主机名、磁盘号、该磁盘中的块号和在该块中的位置。当然, 记录结构和聚集运算符可以重复地使用, 以构造更复杂的类型。例如, 我们可以定 义一个记 录结构 类型, 它的第一 个成分 是字符串 类型, 名为“customer”, 第 二个成分 是整型集合类型, 名为“accounts”, 这种类型适于将银行顾客和他们的帐户集合联系起来。类和对象类包括类型, 可能还有 一个或多个能 在该类的 对象上 执行的函 数或过程 ( 称 为方法,参看下文) 。类的对象或者是属于该类型的值( 称为不变对象) , 或者是其值属于该类型的变量( 称为可变对象) 。例如, 如果我们定义了一个类型为“整型集合”的类C, 则{2, 5, 7}是类 C 的不变对象, 然而, 也可说明变量 s 属于类 C, 且赋值为{2, 5, 7}。对象标识假定每 个对象都有个对 象标识(object identification,OID) 。两 个对象不能有相 同的OID, 一个对象也不能有两个不同的 OID。OID 是该对象的引用所拥有的值。我们通常会认为OID是虚拟存储器中指向该对象的 指针, 但 正如我们在指针与 引用类型的关系 中所讲的, 数据库 系统中的 OID 实际上更 复杂一 些: 是 一个足以 将在任 何大量 的不同 机器 的第二或第三存储器上的对象准确定位的位序列。而且, 由于数据是持久的, 只要数据存在,任何时刻 OID 都必须是有效的。方法与类有 关的通常还有某 些函数, 往 往称为“方法”。类C的一 个方法 至少有一 个类C·11·
何时检验约束在一般情况 下,SQL系统不会允许导 致违背约束的数 据库更新。但是, 有时需要进行几个相关更新, 其中一个会导致违例而另一个则会予以弥补。例如, 在例 6. 3 中我们规定presC# 为Studio的外键码, 将参照MovieExec中的cert# 。如果我们想要插入新的制片公司及其总裁, 那么, 首先插入制片公司将会违背外键码约束。看起来我们可以通过首先向MovieExec中插入总裁来解决这个问题。但是, 假设还有一个约束, MovieExec 元组中的证书号必须或者作为制片公司总裁出现或者作为制片人( 在Movie中) 出现。于是, 没有一种顺序正确。幸运的是, SQL2 赋于我们将约束说明为 DEFERRED( 延迟) 的能力。于是, 直到“事务”( 数据库操作的基本单元, 见 7. 2 节) 完成, 才会对约束进行检验。我们可以将制片公司及其总裁的两个插入都包括在一个事务中, 于是就避免了这种不合逻辑的约束违例。而当我们描述对属性的约束时, 有指向该值的属性的名称。SQL2 通过提供指向域值的特殊的关键字VALUE解决了这个问题。例 6. 8 我们可以通过 CREAT E D"OMAIN GenderDomain CHAR( 1)CHECK(VALUE IN(' F',' M') ) ;说明仅以两个字符 ' F' 和 ' M' 为值的域 GenderDomain。于是, 我们就可以修改图 5. 13 的4) 行使其内容为4)gender GenderDomain,与此类似, 在例 6. 6 中, 我们要求属性 presC# 为 6 位的证书号, 通过把域说明为 CREAT E DOMAIN CertDomain INTCHECK( VALUE > = 100000) ;可以得到同样的效果。如果我们写出属性 presC# 的说明4)presC#CertDomain REF ERENCES MovieExec(cert# )就可以得到所要求的约束。 □6. 3. 4 本节练 习练习 6. 3. 1: 对于关系Movie(title,year,length,inColor,studioName,producerC# )中的属性, 写出下列约束* ( a) 年份不能在 1895 年以前。(b) 电影长度不能短于 60, 也不能长于 250。* ( c) C制片公 司名 只 能为 迪斯 尼 ( Disney) 、福 克斯 ( F ox) 、米高 梅 ( MGM) 或派 拉 蒙( Paramount) 。练习 6. 3. 2: 针对练习 4. 1. 1 的实例模式·542·
Product( maker, model, t ype)PC(model,speed,ram,hd,cd,price)Lapt op( model, speed, ram, hd, screen, price)Printer(model,color,type,price)写出其中属性的下列约束(a) 便携式电脑的速度必须至少为 100。( b) CD 的速度只能为 4, 6, 8 或 12 倍速。(c) 打印机的类型只能为激光(laser) 、喷墨(ink-jet) 和干式(dry) 。( d) 产品类型只能为 PC 机、便携式电脑和打印机。(e)PC机RAM的容量至少为其硬盘容量的 1% 。6. 4 全 局 约 束现在, 我们将对涉及到几 个属性甚至几 个不同 关系之间 的联系 这类更加 复杂的约 束进行说明。该题目分成两个部分:1. 基于元组的CHECK约束, 用于限制单个关系中元组的诸方面;2. 断言, 即涉及到整个关系或覆盖同一关系的几个元组变量的约束。6. 4. 1 基于元 组的CHECK约束我们用CREAT E T ABLE语句定义单个 表R时, 要想对该 表内的 元组说明 约束, 可以在属性表和键码或外键码的说明之后加上关键字 CHECK 和随后括起的条件。该 条件可以是能出现在WHERE子句中的任何内容。把它看作表R中元组的条件。但是, 像基于属性的约束那样, 该条件可以在子查询、其他关系或同一关系 R 中的其他元组中提到。每当把元组插入到R中或者修改R中的元组时, 就 对基于元组的CHECK约束 中的条件进行检验。对于所有的新元组或者修改的元组都要对条件求值。如果对某个元组为假, 则违背了约束并拒绝导致违例的插入或修改操作。但是, 如果条件涉及到子查询中的某个关系 ( 即使是 R 本身) , 并且该关 系的改变 导致对 于 R 的某 个元组 条件变为 假, 则 该检验并不阻止这种改变。也就是说, 像例 6. 7 中讨论的基于属性的CHECK一样, 基 于元组的约束对于其他关系也是不可见的。尽管基于元组 的检验可以涉及 到一些非 常复杂的 条件, 但通常 最好将复 杂的检验 留给 SQL 的“断言”来解决, 关于断言我们将在 6. 4. 2 节讨论。就像我们在上面讨论的那样,其原因是在特定条件下, 可能会违背基于元组的检验。然而, 如果基于元组的检验仅涉及到要检验的元组的属性并且元组没有子查询, 那么约束总是有效的。这里有一个涉及到一个元组中几个属性的基于元组的CHECK约束的简单例子。例 6. 9 回顾一 下例 5. 32, 在那 里我们 说明了表 MovieStar 的模式。图 6. 4 重复 了CREAT E T ABLE语句, 并附加了用关键字UNIQUE说 明的键码以及一 个额外的约束,它是我们可能希望检验的几种可能的“一致性条件”之一。该约束指出如果影星的性别为男性, 那么, 其姓名前面绝对不能加上' Ms.'。·642·
正确书写约束许多约束都和例 6. 9 的情形相类似, 在例中我们想要禁止满足两个或更多条件的元组。符合检验要求的表达式是对每个条件否定( 即取反) 的或( OR) 。因此, 例 6. 9 中第一 个条 件是影 星为 男性, 于 是我 们 用gender=' F'作为 适当 的 否定 ( 尽 管gender〈〉' M' 或许是表示否定的更普通的方式) 。第二个条件是姓名前面带有 ' Ms. ' , 于是我们用NOT LIKE这种比较运算作为它的否定。该比较运算对条件本身取反, 而条件在 SQL 中则为 name LIKE ' Ms. % ' 。 1) >CREA>TE TABLE MovieStar(2) name CHAR( 30) U NIQUE,3) address VARCHAR( 255) ,4) gender CHAR ( 1) ,5)birthdate DATE,6)CHECK(gender='F' OR name NOT LIKE ' Ms. %')) ;图 6. 4 表MovieStar的约束在 2) 行, 说明name为关系的键码。接下来 6) 行说明一个约束。对于所有的女性影星以及姓名前 面不加 ' Ms. ' 的所有影 星, 该约 束条件均为真。只有对 性别为男性而姓 名前面却带有 ' Ms. ' 的元组, 条件不为真。那些恰恰是我们希望从 MovieStar 中排除的元组。□6. 4. 2 断言我们已经将对属性的约束扩展为对元组的约束。但即使这些形式有时也还不够。有时我们需要的 约束涉及到作为 整体的关系, 例如对 一列中的 值求和 或者进行 其他聚合 运算的约束。还会用到涉及到多个关系的约束。事实上, 外键码约束是我们已经看到的连接两个关系的约束的一个例子, 但外键码约束有其局限性。SQL2 的 断 言( assert ion) ( 也 称 为“通 用 约 束”) 允 许 我 们 施 加 任 何 条 件 ( 可 以 跟 在WHERE后面 的表达式) 。其 他类型的约 束同其 他模式元 素相关, 通常 基于表或 域, 而 断言本身就是模式元素。和其他模式元素一样, 我们用 CREATE 语句说明断言。断言的格式为:1. 关键字 CREAT E ASSERT ION,2. 断言的名称,3. 关键字CHECK, 以及4. 括起的条件。即, 语句的格式为CREATE ASSERT ION〈名称〉CHECK(〈条件〉)断言中的条件必须一直为真, 任何使其为假的数据库更新操作都会遭到拒绝。回顾·742·
对限定约束的检验: 缺点还是优点?人们可能会感到奇怪, 如果基于属性和基于元组的检验引用其他关系或同一关系的其他元组, 为什么就允许违约? 其原因是, 实现这样的约束比实现断言更有效。用基于属性或基于元组的检验, 只要对插入或修改的元组的约束进行判断。反之, 每当断言中提到的任何一个关系发生变化, 都必须对断言进行判断。这样额外的判断使数据库更新增加了运行时间, 这样做是否值得, 数据库设计者要对此作出评估。然而, 为了代码的长期可靠性, 我们建议, 设计者不用可能违约的基于属性或基于元组的检验。一下, 我们已经研究过 的其他类 型的 CHECK 约 束, 在 某些条件 下只要 涉及到子 查询, 就会出现违例。我们书写基于元组的 CHECK 约束和书写断言的方法有所不同。基于元组的检 验可以 引 用 关 系 说 明中 出 现 的 属 性。例 如, 在 图 6. 4 的 6) 行 中 我 们用 到 了 属 性gender和name, 而 没有指出 它们 来自何 处。由 于表MovieStar正是在CREAT E语 句中要 说明 的表, 因此这些属性是指 MovieStar 元组的分量。断言的条件就没有这种特权了。条件中引用的任何属性都必须在断言中指明, 典型的方式是在select-from-where表达式中 指出属性所在的 关系。由于条件必 须为布尔值, 通常以某种方法将条件的结果聚合起来得到单一的真/ 假选择。例如, 我们可以将条件写成生成某个关系的表达式, 对其应用NOT EXIST S; 也就 是, 约束为 该关系总为空。作 为选择, 我们可以对关系中的一列 用SU M这样的聚合运算 符并将之与常量 相比较。例如, 用这种方法我们可以要求和总是小于某个限定的值。例 6. 10 假设 我们要求其净 资产没 达到 10 000 000 美 元的人 不能成为 制片公司 的总裁。为此目的我们说明一个断言, 其总裁净资产不足 10 000 000 美元的电影制片公司的集合为空。此断言涉及到两个关系:MovieExec(name,address,cert# ,netWorth)St udio( name, address, presC# )断言如图 6. 5 所示。 CREAT E ASSERTION RichPr es CHECK(NOT EXIST S( SELECT*FROM Studio,MovieExecWHERE presC# =cert#AND netWorth< 10000000)) ;图 6. 5 承认富有的制片公司总裁的断言顺便 说 一 下, 尽管 该 约束 涉 及 到两 个 关系, 却毫 无 价值, 我们 可 以将 它 写成 针 对 这两个关 系 的基 于 元 组 的CHECK约 束 , 而 不 写 成 单 独 的 断 言。例如, 我们可以在例 6. 3·842·
约束的比较下表列出了基于属性的检验、基于元组的检验和断言之间的主要区别。约束类型 说明位置 激活时刻 肯定保持?基于属性的约束 与属性一起 对关系进行插入或修改属性时 如果有子查询则不保持基于元组的约束 关系模式的元素 对关系进行插入或修改元组时 如果有子查询则不保持断言 数据库模式的元素 当提到的任何关系发生改变时 保持的 CREAT E T ABLE 语句中加上如图 6. 6 所示的对 Studio 的约束。 ?1) CREA TE TABLE Studio(2) name CHAR( 30) PRIMARY KEY,3)address VAR CHAR( 255) ,4)presC#INT REFERENCES MovieExec(cert# ) ,5) CHECK ( presC# NOT IN6) ( SELECT cert# FROM MovieExec7) WHERE netWorth< 10000000))) ;图 6. 6 和断言相对应的对Studio的约束但 是要 注意, 只 有在 关 系Studio发 生变 化 时才 对 图 6. 6 中的 约 束 进行 检 验。它 不会检验到这种情形: 已 记 录 在 关 系 MovieExec 中 的 某 个 制 片 公 司 总 裁 的 净 资 产 降 到10 000 000 美元以下。为 达到断言 的全部效 果, 我们 不得不 给表MovieExec的说 明加 上另一个约束, 要求如果行政长官为制片公司总裁, 则净资产至少为 10 000 000 美元。 □例 6. 11 这里是断言的另一个例子。它仅影响到关系Movie( title, year, length, inColor, studioName, producerC# )并且指出给定的制片公司所制作的所有电影的总长度不应超过 10 000 分钟。 CREAT E ASSERT ION SumLengt h CHECK ( 10000 > = ALL ( SELECT SUM( length) FROM Movie GROUP BY studioName) ) ;该约 束碰 巧仅 涉及到 关系 Movie。也 可以 把它 表 示成 Movie 模 式中 的 基于 元组 的CHECK 约 束 而 不 表 示 成 断 言。 即, 我 们 可 以 在 表 Movie 的 定 义 中 加 上 基 于 元 组 的CHECK约束 CHECK ( 10000 > = ALL( SELECT SUM( length) FROM Movie GROU P BY studioName) ) ;注意, 原则上, 这个条件应用于 表Movie的所有元组。然 而, 它 并没有明 确地提到 元组的任何属性, 而所有的工作都是在子查询中完成的。·942·
还应该注意, 如果作为基 于元组的约束来 实现, 当 从关系 Movie 中 删除元组 时, 不 会进行检验。在本例中, 如果约束在删除操作之前能满足, 那么, 保证删除操作之后也满足,所以这种区别无关紧要。但是, 如果本例中约束是总长度的下限而不是上限, 那么我们就会发现作为基于元组的检验而不是断言所写的约束会发生违例。 □6. 4. 3 本节练 习练习 6. 4. 1: 我们在例 6. 10 中提到, 图 6. 6 中的基于元组的 CHECK 约束只能完成图 6. 5中断言所完成的工作的一半。写出完成该项工作所需要的对MovieExec的CHECK约束。练习 6. 4. 2: 对于 我们不 断滚动的 电影实 例的 如下几 个关 系之一, 写 出下 列基于 元组 的CHECK约束。Movie( title, year, length, inColor, studioName, producerC# )StarsIn(movieT itle,movieYear,st arName)MovieSt ar( name, address, gender, birthdat e)MovieExec(name,address,cert# ,netWorth)Studio( name, address, presC# )倘若约束实际 上涉及到两个关 系, 那么, 你应 该把约束 放在两个 关系中, 从而 保证无论 哪个关系发生变化, 都将对插入和修改操作的约束进行检验。假定没有删除操作, 那么保留对删除操作的基于元组的约束就不合理了。* ( a) 1939 年以前制作的电影不会是彩色的。 (b) 影星不会出现在他们出生之前制作的电影中。! ( c) 两个制片公司不会有同一个地址。* ! (d) 在MovieSt ar中出现的姓名绝对不能出现在MovieExec中。! ( e) 出现在 St udio 中的制片公司名称必须至少出现在一个 Movie 元组中。! ! (f) 如果某电影的制片人同时也是某制片公司的总裁, 那么他或她必须是制 作该电影的制片公司的总裁。练习 6. 4. 3: 将下列约束表示成SQL断言。约束基于练习 4. 1. 1 中的关系:Product( maker, model, t ype)PC(model,speed,ram,hd,cd,price)Lapt op( model, speed, ram, hd, screen, price)Printer(model,color,type,price)* ( a) PC 机制造商不可能也制造便携式电脑。* ! (b)PC机制造商必须也制造其处理器速度至少同样快的便携式电脑。! ( c) 如 果便 携 式电 脑的 主存 容量 比 PC 机 大, 那么 便 携式 电 脑 的价 格 也一 定 比PC机高。! ! ( d) 在关系 PC, Laptop 和 Printer 中, 型号不可能出现两次。! ! (e) 如果关系Product提到了某型号及其类型, 那么该型号 一定出现在适合 该类型的关系之中。练习 6. 4. 4: 写出下列关于我们的“PC”模式的基于元组的CHECK约束。·052·
( a) 处理器速度不到 150MHz 的 PC 机一定不能以超过 1 500 美元的价格出售。(b) 屏幕尺 寸不到 11 英寸的便携式 电脑必须具有至少 1G字节的 硬盘, 否则价格 必须低于 2 000 美元。练习 6. 4. 5: 将下列约束表示成SQL断言。约束基于练习 4. 1. 3 中的关系:Classes( class, type, country, numGuns, bore, displacement )Ships(name,class,launched)Battles( name, date)Outcomes(ship,batt le,result)( a) 每个等级不超过两艘舰艇。! (b) 没有哪个国家能够同时有战列舰和巡洋舰。! ( c) ,配 备超 过 9 门火 炮 的舰 艇 在与 配 备不 到 9 门火 炮 的舰 艇 的交 战 中 不可 能 被击沉。! ( d) 没有哪艘舰艇能够在具有该等级名称的那艘舰艇下水之前下水。! (e) 对于每个等级, 都有一艘以等级命名的舰艇。练习 6. 4. 6: 对于我们的“战列舰”模式, 将下列约束写成基于元组的 CHECK 约束。(a) 没有什么等级的舰艇其火炮口径超过 16 英寸。( b) 如果某等级的舰艇具有 9 门以上的火炮, 那么, 其火炮口径一定不超过 14 英寸。! (c) 没有哪艘舰艇能够在下水之前参加战役。6. 5 约 束 的 更 新可以在任何时候增加、修改或删除约束。描述这种更新的方法取决于所涉及的约束是和域、属性、表还是数据库模式有关。6. 5. 1 对约束 命名为了修改或删除已经存在的约束, 约束需要有个名称。作为数据库模式的一部分, 断言的命名总是作为其 CREAT E ASSERTION 语句的一部分。然而, 也可以为其他约束命名。为此, 可以在约束之前加上关键字CONST RAINT和该约束的名称。例 6. 12 我们也可以对主键码或外键码的说明命 名。例如, 我 们可以重写图 6. 1 的2) 行, 以便对指出属性name为主键码的约束命名, 像这样: 2) name CHAR( 30) CONST RAINT NameIsKey PRIMARY KEY,同样, 我们可以通过 4) gender CHAR ( 1) CONST RAINT NoAndroCHECK(gender IN(' F ',' M') )为出现在例 6. 6 中的基于属性的 CHECK 约束命名。例 6. 8 中的域约束可以命名为: CREAT E D "OMAIN CertDomain INTCONST RAINT SixDigits CHECK(VALUE> = 100000) ;·152·
为约束命名记住, 为你写的每个约束命名是一个好主意, 即使你并不相信会用到它。一旦建立了没有名称的约束, 以后当你想以任何方式更改它时再命名就太晚了。最后, 下面的约束: 6)CONST RAINT RightT itleCHECK(gender=' F' OR name NOT LIKE ' Ms. %')是为给约束命名而对图 6. 4 中 6) 行的基于元组的 CHECK 约束的改写。 □6. 5. 2 更改表 的约束我们可以用ALT ER语句来更改与域、属性或表有关的约束的集合。在 5. 7. 4 节我们讨论了ALT ER T ABLE语 句的某些用法, 在那里是用 于增加或删除属 性。与此类似, 我们在 5. 7. 6 节讨论了 ALT ER DOMAIN, 用它来改变默认值。这些语句也能用来更改约束;ALT ER TABLE既用于基于属性的检验也用于基于元组的检验。我们可以用关键字DROP和要撤消的约束名来撤消约束。我们也可以用关键字 ADD 随后加上要增加的约束名来增加约束。例 6. 13 让我们看看如何撤消和增加例 6. 12 中的约束。首先, 可以通过ALTER T ABLE MovieStar DROP CONST RAINT NameIsKey;把表明name为关系MovieSt ar的主键码的约束撤消。同一关系中, 限制属性gender取值的基于属性的CHECK约束, 可以通过ALT ER T ABLE MovieStar DROP CONST RAINT NoAndro;来撤消。另外, 关系 MovieStar 中限于女影星的称呼 ' Ms. ' 这一约束可以通过ALT ER T ABLE MovieStar DROP CONST RAINT RightT itle;来撤消。如果我 们想要恢 复这些 约束, 可以通过 增加同 样的约束 来更改关 系 MovieStar 的 模式, 例如: ALT ER T A 0BLE MovieSt ar ADD CONST RAINT NameIsKeyPRIMARY KEY(name) ; ALT ER T ABLE MovieSt ar ADD CONST RAINT NoAndroCHECK ( gender IN ( ' F' , ' M' ) ) ; ALT ER T ABLE MovieSt ar ADD CONST RAINT Right T itleCHECK ( gender = ' F' OR name NOT LIKE ' Ms. % ' )现在这些约束是基于元组的, 而不是基于属性的。尽管属性类型为域, 我们可以用更改域的约束来代替更改MovieSt ar表, 却无法使之恢复为属性约束。这些重新引入的约束其名称是任选的。但是, 我们不能依赖 SQL 来记住同约束 名相关的约束。因此, 当我们增加的是以前的约束时, 还需要重新写出约束; 不能仅通过名称来·252·
引用它。 □6. 5. 3 更改域 的约束从本质上来说, 撤消和增加关于域的约束同撤消或增加基于元组的检验, 其方法是一样的。要想撤消对域的约束, 用ALT ER语句, 其中关键字DROP后面加上约束名。要想增加对域的约束,ALT ER语句中要有关键字ADD、约束名以及定义约束的CHECK条件。例 6. 14 证书号至少有六位, 该域约束可以通过ALT ER DOMAIN CertDomain DROP CONST RAINT SixDigits;来撤消。反之, 我们可以通过ALT ER DOMAIN CertDomain ADD CONSTRAINT SixDigitsCHECK ( VALUE > = 100000) ;来恢复该约束。 □6. 5. 4 更改断 言要撤消断言可以在关键字DROP ASSERT ION后面加上断言名。例 6. 15 例 6. 10 中的断言可以通过语句DROP ASSERTION RichPres;来撤消。要想恢复该约束, 像在例 6. 10 中所作的那样对其重新说明。 □6. 5. 5 本节练 习练习 6. 5. 1: 说明如何用下列方法更改电影实例中的关系模式:Movie( title, year, length, inColor, studioName, producerC# )StarsIn( movieT itle, movieYear , starName)MovieSt ar(name,address,gender,birthdate)MovieExec( name, address, cert# , netWorth)Studio( name, address, presC# )* ( a) 使 title 和 year 成为 Movie 的键码。 (b) 要求的参照完整性约束是, 每部电影的制片人都出现在MovieExec中。 (c) 要求电影的长度不能短于 60 也不能长于 250。* ! (d) 要 求一 个名 字不能 同时 作为影 星和 电影 行政长 官出 现( 该约 束在删 除的 情况下不必保持) 。! (e) 要求两个制片公司不能有相同的地址。练习 6. 5. 2: 说明如何更改“战列舰”数据库模式Classes(class,type,country,numGuns,bore,displacement)Ships( name, class, launched)Battles( name, date)Outcomes(ship,batt le,result)来得到下列基于元组的约束。·352·
( a) 等级和国家构成关系 Classes 的键码。(b) 要求的参照完整性约束是, 出现在Battles中的每艘舰艇也都出现在Ships中。( c) 要求的参照完整性约束是, 出现在 Outcomes 的每艘舰艇也都出现在 Ships 中。(d) 要求没有舰艇配置 14 门以上的火炮。! ( e) 不允许舰艇在下水之前参战。6. 6 SQL3 中的触发程序本章中我们所研究的不同形式的约束都遵循 SQL2 标准。它们有其执行模式, 每当它们所约束的元素发生变化时就按其执行模式调用这些约束。例如, 每当某个元组中的某个属性发生变化时( 包括插入元组的情况) , 就调用基于该属性的检验。由于约束 的实现涉及到对 相应事件的 检验进行“触 发”, 所以, 人们 很自然地 会问, 能否由数据库编程人员而不是由系统来选择触发事件。这种方法会给用户某些附加的选项,以便有目的地触发数 据库操作, 而不是防止出现 约束的违例。因此,SQL3 的推荐标 准还包括了“触发”, 触发使 人联想到约束, 但触发 程序要明 确指定触 发事件, 并明 确指定基 于条件结果而要做的动作。有趣的是, 目前的商业系统在其强大的主动性元素方面往往更接近 SQL3 而不是 SQL2。原因可能 是对商业开发商 来说, 在某 种意义上, 触发程序比断 言更容易实现。6. 6. 1 触发和 约束触发( trigger) 有时也称为事件-条件-动作规则( event-condition-action rules) 或 ECA规则, 在三个方面不同于前面讨论的约束类型。1. 当数据库编程人员所指定的某些事件发生时才对触发程序进行测试。允许的事件种类通常为对特定关系的插入、删除或修改。在许多SQL系统中另一种允许的事件 为事务结束( 参见 7. 2 节对用于批量数据库操作的称为事务的原子工作单元的讨论) 。2. 不是直接阻止事件的发生, 而是由触发程序对条件进行测试。如果条件不满足, 则什么也不做, 否则, 为响应该事件就会进行与该触发相关的处理。3. 如果触发条件得到满足, 就由DBMS执行与该触发相关的动作。于是该动作可能阻止事件的发生或撤消事件( 如删除插入的元组) 。实际上, 动作可能是数据库操作的任何序列, 甚至可能是和触发事件毫无关联的操作。下面, 我们将首先考虑 SQL3 中的触发程序。然后, 我们将简单地讨 论对于 SQL2 中称为“断言”的约束在SQL3 中的扩展。这些约束也含有触发的某些方面。6. 6. 2 SQL3 触发程序SQL3 触发语句在事件、条件和动作部分给用户许多不同的选项。这里是主要特点。1. 动作可以在触发事件之前、之后执行, 甚至可以不用触发事件而执行。2. 动作可以引用在触发该动作的事件中插入、删除或修改的元组的旧值或新值。3. 修改事件可以指定特定的属性或属性集。·452·
的对象作为参数。它可能有包括 C 在内的任何类的其他参数。例如, 可能有一个方法与一个 类型 为“整型集 合”的类 相关, 而用 该方 法可 以计算 给定 集合的 幂集, 求 两个集 合的 并集, 或返回一个布尔值以指出集合是否为空。抽象数据类型在许多情形下, 类也是抽象数据类型, 意思是类封装或限制对类对象的访问, 因此, 只有为类定义的方法才能直接修改类的对象。这种限制保证了类对象不以类的实现者所不期望的方式进行修改。人们把这种概念看作是可靠软件开发的关键工具之一。类的分层结构可以说 明一个 类 C 是 另一个类 D 的子 类。这样的 话, 类 C 将会 继承 类 D 的 所有 特性, 包括D的类型和为D定义的任何函数。然而,C可能有一些附加的特性。比如, 可以为类 C 的 对象定义新的方法, 这些方 法可以是 D 的方 法的补充, 也可以代替 D 的 方法。甚至还可以用某些方式扩展D的类型。尤其是, 如果D属于记录结构类型, 我们就能在此类型的基础上为 C 类增加新的域, 这些域只出现在 C 类的对象中。例 1. 3 考虑某银行帐户类的对象。我们可以非正式地将该类的类型描述为: CLASS Account= { accountNo:integer;balance: real;owner:REF Customer;}也 就 是 说, 类 Account 的 类 型 是 记 录 结 构, 包 含 三 个 域: 整 型 的 accountNo、实 型 的balance以及owner, 而户主 是对 类Customer( 顾客类, 是 我们 在银行 数据 库 中需 要的 另一个类, 但它的类型我们在这里不作介绍) 的对象的引用。我们也可以为类定义一些方法。例如, 我们可能有一个方法deposit( 存款) , 用来将类Account 的对象 a 的结余增加 m: deposit ( a: Account, m: real)最后, 我们也许希望有一 些 Account 的子类即明细帐 目。例如, 一个定期存款( time-deposit) 帐户可能有一个附加的 域dueDate( 支 付日期) , 指明户主 取出结余的日期。 在子类 T imeDeposit 中也可能有另一个方法 penalty( 违约金) : penalt y ( a: T imeDeposit )该方法取出属于子类 T imeDeposit 的帐户 a, 并计算因提前支取的违约金。违约金是对象a的dueDate域和当前日期的函数; 当前日期可以从该方法运行的系统上得到。本书中我们将会广泛地考虑数据库系统面向对象的概念。在 2. 1 节我们会介绍面向对象的数据库设计语言ODL。第 8 章将专门介绍面向对 象的查询语言, 包括正在成 为面向对象 DBMS 标准的查询语言 OQL, 以及为关系 DBMS 的标准查询语言 SQL 推荐的面向对象的特性。·21·
4. 条件可以通 过 WHEN 子句指 定, 而 只有在 对规则进 行触发 并且当触 发事件发 生时条件满足的情况下动作才会执行。5. 编程人员可以对规定执行的动作进行选择:(a) 对于每个更新的元组都执行一次, 或( b) 对于在一个数据库操作中发生变化的所有元组执行一次。在给出触发程 序的语法细节之 前, 让我 们研究 一个能阐 明最重 要的语法 以及语义 特点的例子。在本例中, 对于每个修改的元组都执行一次触发程序。例 6. 16 我们将写出应用于表MovieExec(name,address,cert# ,netWorth)的 SQL3 触发程序。触发程序是由对 netWorth 属性的修改而启动的。该规则的作用是对降低电影行政长官净资产的任何尝试加以阻止。触发程序的说明如图 6. 7 所示。 1) CREATE TRIGGER NetWorthTr igger2) AFTER UPDATE OF netWorth ON MovieExec3) REFERENCING4)OLD AS OldTuple,5) NEW AS NewTuple6) WHEN(OldTuple.netWorth>NewTuple.netWorth)7)UPDATE MovieExec8) SET netWorth = OldTuple. netWorth9)WHERE cert# =NewTuple.cert#10) FOR EACH ROW图 6. 7 SQL3 的触发程序1) 行给出具有关键字CREAT E T RIGGER和触发程序名的说明。然后, 2) 行给出了触发事件, 即修改关系 MovieExec 中的属性 netWort h。3) 到 5) 行是为该触发程序的条件和动作部分如何引用旧元组( 修改前的元组) 和新元组( 修改后的元组) 提供一种方法。根据 4) 行和 5) 行中的说明, 将用 OldTuple 和 NewT uple 分别引用这两个元组。在条件和动作部分, 这些名称可以像在普通SQL查询的FROM子句中说明的元组变量一样使用。6) 行是触发程序的条件部分。它表明只有在新的净资产低于旧的净资产( 也就是某行政长官的净资产缩减) 时才执行动作。7) 行到 9) 行构成动作部 分: 它们 是普通的SQL修改 语句, 而具 有的功能是将该 行政长官的净资产恢复到修改以前的值。注意, 原则上, 会考虑每个 MovieExec 元组, 但是 9)行的WHERE子句保证只影响到修改的元组( 具有特定cert# 的元组) 。最后, 10) 行表明了要求, 即每当修改元 组, 该触发 程序都会启动一 次。如 果没有这一行, 那么, 一个SQL语句使触发程序执行一次而无论发生多少改变元组的触发事件。 □当然, 例 6. 16 仅说明了 SQL3 触发程序的某些特点。在下面的要点中, 我们将概述触发程序提供的选项以及如何描述这些选项。· 图 6. 7 中 的 2) 行如关键字AF TER所指, 其规 则的动作 将在触 发事件之 后执行。可供选择来代替AFT ER的有(a) mBEFORE。WHEN中的条件在触发事件之前检验。如果条件为真, 则执行触·552·
发程序的动作。此外, 无论条件是否为真, 都将执行触发修改的事件。(b) pINSTEAD OF。( 如果符合WHEN中的条件) 会执行动作, 而永远不会执行触发事件。· 除UPDAT E以外, 其他可能的触发事件有INSERT和DELET E。图 6. 7 的 2) 行中的 OF netWorth 子句是 UPDAT E 事件的选项, 并且现在假定把事件仅定 义为修改关键字OF后面列出的属性。OF子句不支持INSERT或DELETE事件; 这些事件只对整个元组有意义。· 尽管 我们用单 个的SQL语句来表 示动作, 其实 可以有 任意数 量用 分号分 开的 这类语句。· 当触发事件为修改时, 有旧元组和新元组, 分别为修改前后的元组。我们用在 4) 行和 5) 行 看到的OLD AS和NEW AS子 句为这 些元 组命 名。如果 触发事 件是 插入, 那 么我们可以用NEW AS子 句为插入 的元组命 名, 而OLD AS则不 予接受。相反, 对于删除操作, 将用OLD AS为删除的元组命名, 而NEW AS则不予接受。· 倘若我们略掉 10) 行中的FOR EACH ROW, 那么像图 6. 7 这样的行级触发程序(row-level trigger) 就变成了语句级的触发程序。对于生成一个或多个触发事件的一个语句, 语句级的触发程序只执行一次。例如, 如果我们用SQL的修改语句修改整个表, 那么, 语句级的修改触发程序只执行一次, 而元组级的触发程序对每个元组都执行一次。在语句级的触发程序中, 我们不能像在 4) 和 5) 行所做的那样直接引用旧的或新的元组。相反, 我们可以将旧元组的集合( 删除的元组或修改的元组的旧版本) 和新元组的集合( 插入的元组或修改的元组的新版本) 作为两个关系来引用。我们用诸如OLD-T ABLE AS OldStuff或NEW-T ABLE AS NewStuff这样的说明来代替图6. 7 中 4) 行和 5) 行的说明。像上面定义的那样, OldStuff 命名了包含所有旧元组的关系, 而NewStuff则指向包含所有新元组的关系。例 6. 17 假设我们想要防止电影行政长官的平均净资产降到 500 000 美元以下。对如下关系MovieExec(name,address,cert# ,netWorth)中 netWorth 列的插入、删除或修改操作可能会违背该约束。我们需要为这三个事件中的每一个写一个触发程序。图 6. 8 描述了修改事件的触发程序。插入和删除元组的触发程序与此类似, 但稍微简单一些。3) 到 5) 行说明NewStuff和OldSt uff是 关系名, 这两个 关系包 含触发我 们的规则 的数据库操作所涉及到的新元组和旧元组。注意, 一个数据库语句可以修改一个关系中的许多元组, 而如果执行了这样的语句, 那么, 在NewStuff和OldSt uff中就会有许多元组。如果是修改操作, 那么, NewStuff 和 OldStuff 分别为修改的元组的新版本和旧版本。如果为删除操 作写出类似的触 发程序, 那 么, 删除 的元组应该在OldSt uff中, 而不会有 像该触发程序中对应于 NEW- T ABLE 的 NewStuff 这样的关系名的说明。同样, 在插入操作的类似的触发程序中, 新元组在NewStuff中, 而不会有OldStuff的说明。6) 到 8) 行是条件。如果修改后的平均净资产至少为 500 000 美元, 则条件满足。注意,8) 行中的表达式计算的是如果修改操作已经做完的MovieExec关系。·652·
1) CREATE TRIGGER AvgNetWorthTrigger2) INSTEAD OF UPDATE OF netWorth ON MovieExec3) REFERE NCING4)OLD- TABLE AS OldStuff5) NEW- TABLE AS NewStuff6) WHENv( 500000< =7) ( SELECT AVG( netWorth)8)FROM( (MovieExec EXCEPT OldStuff)UNION NewStuff))9) DELETE FROM MovieExec10) WHERE(name,address,cert# ,netWorth)IN OldStuff;11) INSERT INT O MovieExec12) (SELECT*FROM NewStuff) ;图 6. 8 对平均净资产的约束但是, 由于 2) 行中规定 了 INST EAD OF, 任 何对 MovieExec 的 netWort h 列 进行 修改的尝试都将截取下来。永远不会执行修改操作。作为替代, 触发程序利用其条件来判断该做什么。在我们的实例中, 如果修改操作保留电影行政长官的平均净资产至少五十万,那么动作就会得到修改操作所期望得到的效果。也就是说, 9) 行和 10) 行删除修改操作想要修改的元组, 而 11) 行和 12) 行则插入这些元组的新版本。 □6. 6. 3 SQL3 的断言SQL3 还在两个重要的方面扩展了SQL2 断言。1. 由程 序员规定 的事件 触发断言, 而不 是由系 统决定、可能和 约束相 违背的 事件 触发断言。2. 断言 就像元组 级的检 验, 可 以任意指 向表中 的每个元 组, 而 不是指 向作为 整体 的表或多个表。例 6. 18 用 SQL3 的表 示法, 例 6. 10 中的 RichPres 断言如 图 6. 9 所 示。像通常 那样, 1) 行开始说明。在 2) 到 6) 行, 我们看到可以触发对断言进行检验的多个事件。 1) CREATE ASSERTION RichPres2) AFTER3) INSERT ON Studio,4)UPDATE OF presC#ON Studio,5)UPDATE OF netWorth ON MovieExec,6) INSERT ON MovieExec7) CHECK(NOT EXISTS8) ( SELECT * FROM Studio, MovieExec9) WHERE presC# =cert#AND netWorth< 10000000) )图 6. 9 SQL3 断言回顾一下, 为了截取 与 7) 到 9) 行的约束 相违背的 对数据库 所有可 能的改变, 我们 需·752·
要注视新任的制片公司总 裁或者某个行政 长官净资产的变 化。因此, 每 当插入 Studio 元组或者修改制片公司总裁的证书号( 也就是总裁人 选发生变化) 时, 3) 行和 4) 行将导 致对断言的检验。当修改任何行政长官的净资产或者插入某个行政长官时, 两种情况中的任何一 种都可能 导致约 束为假, 这时 5) 行和 6) 行 就会触发 检验。要检 验的约 束位于 7) 行 到9) 行, 而在本质上和例 6. 10 是相同的。 □SQL3 和SQL2 在断 言方 法上的 主要 区别 在于, 当需 要进 行检验 时图 6. 9 将 断言 显式地表示出来。这种情形使 SQL3 断言对系统实现者更加容易, 但对如下的用户就比较困难了:1. 必须发现所有可能触发约束的事件;2. 当事件选择不恰当时, 甘于冒允许数据库进入不一致状态的风险。6. 6. 4 本节练 习练习 6. 6. 1: 为 MovieExec 的插入和删除事件写出与图 6. 8 相似的 SQL3 触发程序。练习 6. 6. 2: 基于练习 4. 1. 1 中的“PC”实例:Product( maker, model, type)PC(model,speed,ram,hd,cd,price)Laptop( model, speed, ram, hd, screen, price) Print er(model,color,type,price)写出下列 SQL3 触发程序或断言:* (a) 当修改PC机的价格时, 检验没有速度相同而价格更低的PC机。 ( b) 当插入新的打印机时, 检验其型号存在于 Product 中。! (c) B当对Lapt op关系进行任何更 新时, 检 验每个厂商的便携 式电脑的平均价 格至少为 2 000 美元。! (d) L当对任何PC机的RAM或硬盘进 行修改时, 检验修改的PC机的硬盘至 少为RAM 容量的 100 倍。! (e) E当插入新的PC机、便 携式电脑或 打印机时, 保 证该型号 以前没有 出现在PC,Laptop 或 Print er 中。练习 6. 6. 3: 基于练习 4. 1. 3 中的数据库模式Classes( class, type, country, numGuns, bore, displacement )Ships(name,class,launched)Battles( name, date)Outcomes(ship,batt le,result)写出一个或多个 SQL3 触发程序或断言来完成下列要求:* (a) U当 向Classes中 插 入 新 的 等 级 时, 还 要 插 入 一 艘 以 等 级 命 名 而 下 水 日 期 为NULL 的舰艇。 (b) 当插入排水量大于 35 000 吨的新等级时, 允许插入, 但要将排水量改为 35 000。! ( c) j如 果 把 元 组 插 入 到 Outcomes 中, 应 检 验 舰 艇 和 战 役 分 别 列 在 Ships 和Battles中, 如果没有, 则将元组插入到这两个 关系中或其中之 一, 必要的 分量·852·
为 NULL 值。! (d) g当向Ships插入或者对Ships的class属性进行修改时, 检验没有国家具有 20艘以上的舰艇, 否则取消插入。! ! (e) `在所 有可能导致违例 的情况下, 检验没 有舰艇能 够参加 比导致该 舰艇沉没 的那场战役更晚的一场战役。否则防止更新操作的发生。! 练习 6. 6. 4: 将下列要求写成合适的SQL3 触发程序或者SQL3 断言。问题基于我们不断滚动的电影实例:Movie(title,year,length,inColor,studioName,producerC# )StarsIn( movieT itle, movieYear , starName)MovieSt ar(name,address,gender,birthdate)MovieExec( name, address, cert# , netWorth)Studio(name,address,presC# )可以假定在尝试对数据库做任何改变之前, 要求的条件都成立。同样, 即使意味着插入带有NULL值或默认值的元组, 也宁愿更新数据库而不愿拒绝更新尝试。( a) 确保在任何时候, 出现在 StarsIn 中的任何影星也出现在 MovieStar 中。(b) 确保在任何时候, 所有的电影行政长官或者作为 制片公司总裁出 现, 或者作 为电影制片人出现, 或者作为二者出现。(c) 确保所有的电影都至少有一个男影星和一个女影星。( d) 确保任何制片公司在任何年份制作的电影数量都不超过 100。(e) 确保在任何年份制作的所有电影的平均长度都不超过 120。! 练习 6. 6. 5: 在例 6. 17 中处理不利的更新的方法是, 首先检验, 然后更新 —— 如果 它不和条件相违背的话。另一种方法是允许更新, 然后复原 —— 如果它和条件相违背的话。写出该触发程序。6. 7 本 章 总 结 键码约束: 我们可以用 关系模式中 的 UNIQUE 或 PRIMARY KEY 说明来说 明属性或属性集为键码。 参照完整性 约束: 我们可以 用关系模 式中的 REFERENCES 或 FOREIGN KEY说明来 说明出 现在某个 属性或属 性集中 的值也必 须出现 在另一个 关系某 个元 组的主键码属性中。 基于 属性的检 验约束: 我 们可以 在关 系模式 中某 个属 性说明 的后 面加上 关键 字CHECK 和要 检验的条件来检 验对该属性值 的约束。作 为选择, 我们 可以将域 作为属性类型并在域的说明中指定要检验的条件。 基于元组的检验约束: 我们可以在关系本身的说明中加上关键字 CHECK 和要检验的条件来检验关系中元组的一些或所有分量的条件。 断言: 我们可以用关键字 CHECK 和要检验的条件来说明断言为数据库模式的元素。该条件可能涉及到数据库模 式的一个或多个 关系, 还可 能涉及到作为整 体的·952·
关系( 例如, 聚合) 以及单个元组的条件。 检验 的调用: 每当断 言所 涉及的 关系 之一发 生变 化而 使得可 能和 约束发 生违 背时, 就对断言进行检验。仅当对元组的插入或修改操作使得 基于值的和基于 元组的检验约束所作用的属性或关系发生变化时, 才对约束进行检验。因此, 如果约束有涉及到其他关系或同一关系的其他元组的子查询时, 就可能违背这些约束。 SQL3 触 发程序:SQL3 的推荐 标准 包含 详细 说明 某些 事 件( 如对 特 定关 系的 插入、删除或修改) 的触发程序, 而这些事件将启动触发程序。一旦启动触发程序, 就对条件进行检验, 如果为真, 则执行特定的动作序列 ( 诸如查 询和数据库更新 之类的 SQL 语句) 。 SQL3 断言:SQL3 标准包含了一种不同于SQL2 断言的断言概念。像SQL3 触发程 序一样, 这些断 言将为 一个或多 个诸如 向关系中 进行插 入之 类的 事件所 启动。一旦启动,SQL3 断言就将检验关系或元组的条件, 若条件不满足, 则拒绝更新。6. 8 本章参考文献读者应该回到第 5 章的文献目录评述中以得到如何获取SQL2 或SQL3 标准文 档的信息。参考文献[ 4] 是关于数据库系统中主动性元素所有情况的信息源。[ 1] 讨论了关于SQL3 和未来标准中主动性元素的新 近想法。参考文献 [ 2] 和 [ 3] 讨 论了一个提供主 动性数据库元素的早期原型系统 HiPAC。[ 1] +Cochrane, R . J. , H. Pirahesh and N. Mattos, Integr ating triggers and declarative constraints inSQL database systems.Intl.Conf.on Very Large Database Systems,pp. 567~579, 1996.[ 2 ] gDayal,U. ,et al. ,The HiPAC project:combining active databases and timing constraints.SIGMOD Recor d, 17: 1, pp. 51~70, 1988.[ 3] 2McCar thy,D.R. ,and U.Dayal,The architecture of an active database management system,Proc. ACM SIGMOD Intl. Conf. on Management of Data, pp. 215~224, 1989.[ 4] 'Widom,J. ,and S.Ceri,Active Database Systems,Morgan-Kaufmann,San Francisco, 1996.·062·
第 7 章 S Q L系 统 概 况 现在我们来谈谈如何把 SQL 用于完整的编程环境中。下面提到的每个问题, 都 将遵循SQL2 标准。在 7. 1 节, 我们将看到SQL通常用于由一般 编程语言( 例如C) 编写 的程序中。SQL 的许多特性允许我们在它内在的关系和外在的宿主语言变量之间传递数据。接 下来, 7. 2 节 介绍“事 务”—— 工作 的原子 单位。许 多数 据库的 应用, 例如 银行 业务, 即 使可能同时进行大 量的并发操作, 也需 要对数据 的操作表 现出原 子性即不 可分性。SQL提 供的特性 允许我 们来描述 事务, 并 且SQL系 统具 有这 样的机 制来 保证事 务操 作能够真正原子化地执行。7. 3 节涉及 到其他的 系统问题, 例 如, 对客 户程序/ 服务 程序 计算模 型的 支持。接 下来, 7. 4 节将讨论 SQL 如何控制对数 据的非法访问, 以及我们如 何告诉 SQL 系统什 么样的访问是合法的。7. 1 编程环境中的 SQL到目前为止, 我们在例子中都在使用直接的SQL。也就是说, 我们假定已经有一个图 7. 1 处理具有嵌入式 SQL 语句的程序SQL 解 释 程序, 它 接受 和 执 行 各 种已 经 学 过 的SQL查询 和命令。 这种操 作方 式现实 中很 少使用。实际上, 大多数 SQL 语句都是某种更大的程序或者函数集的一部分。一个更现实的方式是用某种传统 的宿主语言 ( 例如 C) 编写 程序, 但是该程序 中的某 些函 数或者C程序中 的某 些语 句实际上是 SQL 语句。在这一节, 我们将叙述在传统的程序内部进行SQL操作的方法。包括 SQL 语句 的典型编程 系统的示 意图如图 7. 1 所示。在这里我们可以看到程序员用宿主语 言 来 编 写 程 序, 但 是 其 中 某 些 特 殊 的 嵌 入 式SQL语句 不是宿 主语 言的 一部分。首 先把 整个程 序 送 到 预 处 理 程 序, 该 预 处 理 程 序 把 嵌 入 式SQL语 句 转换 成 在 宿 主 语言 中 有 意 义 的 内 容。SQL 语 句的这种 表示法 能够简化 成函 数调用, 该 函数 调用把 SQL 语 句作 为字符 串参 数并且执行该SQL语句。我们还在 图 7. 1 中 表示了 程序员直 接用宿 主语言写 程序的可 能性, 如果需要就使用这些函数调用。·162·
SQL2 标准支持的语言SQL2 的实现至少要支持以 下七种宿主语言:ADA,C,Cobol,Fortran,M( 以前称作 Mumps) , Pascal 和 PL/ I。可能除了 M( 即 Mumps, 一种主要用于医学界的语言) 以外, 学习计算机科学的学生应该熟悉以上的每种语言。在我们的例子中将使用C。然后, 预处理过的宿主语言以通常的方式编译。数据库管理系统供应商通常提供函数库以提供必要的函数定义。这样, 就能执行实现 SQL 的函数, 而整个程序则表现为一个整体。7. 1. 1 匹配失 衡问题连接SQL语 句和传统 编程语 言的一个 基本问 题是匹配 失衡, 就是说SQL的数据 模型与其他语言的数据模型差别非常大。我们知道,SQL使用关系数据模型作为它的核心。然而, C 和其他普通的编程语言使用的数据模型具有整数、实数、算术运 算、字符、指针、记录结构、数组等等。C和其他一些 语言不能直接表示 集合, 而 另一方面,SQL又不直接 使用指针、数组或者其他普通的编程语言结构。从而 SQL 和其他语言之间的转换不是 直接的, 所以要研究一种机制允许同时使用SQL和另一种语言来开发程序。首先一个可能 的假设是 最好使用 单一的语 言, 或者用SQL完成所 有的计算, 或者 忘记 SQL 而用传统的语言完成所有的计算。然而, 当涉及到数据库操作时我们就会很 快抛弃无视SQL的想法。SQL系统 可以给程序员编 写既高效率执行又 高级别表达的数 据库操作以极大的帮助。SQL 可以减少程序 员的如下工作: 了解数据在 存储器中是如何 组织的或者如何利用存储结构来有效地操作数据库。在另一方面, 有许多重要的事情SQL并不能做。例如, 不能利用SQL查询来计 算一个数 n 的阶乘 [ n! = n× ( n- 1)× …× 2× 1] , 但是这种事情很容易用 C 或者其他类似的语言实现。SQL不能把它的输出直接格式化为图形那样方便的形式。所以, 实际的数据库编程同时需要 SQL 和传统的语言, 而后者通常称为宿主语言( host language) 。7. 1. 2 SQL/ 宿主语言接口在数据库 ( 只能通过 SQL 语句访 问) 和 宿主语 言程序之 间是通 过宿主语 言变量来 传递信息的( 可以用SQL语句读或写宿主语言变量) 。当SQL语句引用所有这些共享 变量时, 变量前面都加上冒号, 但是在宿主语言语句中变量前没有冒号。当我们想在宿主语言程序中使用 SQL 语句时, 我们要警告: 在 SQL 语句之前要 加上关键字EXEC SQL。一 个典型的系统将 利用与SQL有关的 库对这 些语句进 行预处理 并用宿主语言中相应的函数调用来代替它们。在SQL2 标准中 称为SQLSTAT E的 特殊变 量用 于连接 宿主 语言 程序和SQL执 行系统。①SQLST AT E的类型是 5 个 字符的数组。每当调 用SQL库中的函 数时, 就把 一个·262·①没 有 实 现SQL2 标 准 的 系 统 可 能 使 用不 同 于SQ LST A T E的 名 字, 但是 我 们 预 期 会 找 到 起 该 作 用 的 某 个变量。
代码放入变量 SQLST AT E 中, 以表示调用期间发生的任何问题。例如, ' 00000' ( 5 个 0)表示没有错误发生,'02000'表示没有找到作为SQL查询应答所要的元组。宿主语言程序能够读出SQLST AT E的值并且基于该值作出判断。7. 1. 3 说明(DECLARE) 段要说明共享的变量, 就要把变量的说明放在两个嵌入式 SQL 语句之间:EXEC SQL BEGIN DECLARE SECTION;…EXEC SQL END DECLARE SECT ION;中间的内容称为说明段。说明段中变量说明的格式是宿主语言所要求的。而且, 只有所说明的变量属于宿主语言和SQL都能处理的类型才有意义, 例如, 整数、实数以及字符 串即字符数组。例 7. 1 下列语句可能出现在更新Studio关系的C函数中。 IEXEC SQL BEGIN DECLARE SECTION;char st udioName[ 15] ,studioAddr[ 50] ;char SQLST AT E[ 6] ;EXEC SQL END DECLARE SECT ION;前 后 两 句 是 说 明 段 的 开 始 和 结 束 所 必 需 的。 中 间 的 一 个 语 句 说 明 了 两 个 变 量studioName 和 studioAddr。它们都是字符数组, 像我们将会看到的那样, 它们将用来保存制片公司的名字和地址, 名字和地址 将作为一个元组 插入到 Studio 关系中。第三句 说明SQLST AT E 是 6 个字符的数组。①7. 1. 4 使用共 享变量在SQL语句中可以用共享变量来代替具体的值。使用共享变量时需要在前面加上冒号。这里有一个例子, 我们使用例 7. 1 的变量作为将要插入到关系 Studio 中的元组分量。例 7. 2 在图 7. 2 中, 我们看 到 C 函 数 getStudio 的概要, 该函 数提示用 户输入制 片公司的名字和地址, 读取数据, 并把合适的元组插入到关系Studio中。1) 到 4) 行是我们在例 7. 1 中学 过的说明。我们省略 了C的代码, 这些代码 将打印请求信息并 输入用于填 写数组studioName和st udioAddr的数据。接下来, 5) 和 6) 行是一个由普通的INSERT语句构成的嵌入式SQL语句。该语句用关键字EXEC SQL作为前导以表明它实际上是嵌入式SQL语句而不是不合语法的C代码。在图 7. 1 中所提到的预处理程序将查看EXEC SQL以检测那些必须预处理的语句。由 5) 和 6) 行所插入的值不是显式常数, 如前面的例 5. 27 那样的显式常数。相反地,6) 行中出现的参数是共享变量, 其当前值将成为插入元组的分量。 □·362·①我 们用 6 个 字符 来表示 5 个字符 的SQL ST A T E的值 , 因 为在 随后的 程序中 , 我们要 用C的函数strcmp来检验SQ LST AT E是否具 有某个 值。由于strcmp要求字 符串 以'\ 0'结 束, 因此 需要第 6 个 字符 作为结 束标 志。第 6 个字符必须 初始 化为 ' \ 0' , 但 是我 们不会 在随 后的程 序中给 出该 赋值。
void getStudio( ) {1) EXECTSQL BEGIN DECLARE SECTION;2) char studioName[ 15] , studioAddr [ 50] ;3) char SQLSTATE[ 6] ;4) EXEC SQL END DECLARE SECTION;/ * 打印请求信息, 输入制片公司的名字和地址, 并把回答填写到变量studioName和studioAddr中 * /5) EXEC SQL INSERT INTO Studio(name,address)6) VALUES( : studioName, : studioAddr) ;}图 7. 2 使用共享变量插入新的制片公司除了 INSERT 语句之 外, 还 有许 多 SQL 语句都 可以 用共 享变量 作为 接口嵌 入到 宿主语言中。每个嵌入式SQL语句在宿主语言程序中都以EXEC SQL作为前导, 并可引用共享变量代替常数。任何不返回结果的SQL语句( 即, 不是查询语句) 都可以嵌入到 宿主语言中。嵌入式 SQL 语句的例子包括删除和修改语句以及建立、更改或撤消类似于 表和视图那样的模式元素的语句。然而,select-from-where查询是不能直接嵌入的。因为匹配失衡, 查询不能简单地嵌入到宿主语言中。查询产生元组集合作为结果, 然而没有任何一种主要的宿主语言直接支持集合数据类型。这样, 嵌入式 SQL 必须使用以下两种机制之一以便把查询结果和 宿主语言程序连接起来。1. 只产生一个元组的查询可以把该元组存储在共享 变量中, 元 组的每个分量对 应一个变量。为了这样做, 我们使用select-from-where语句的变形, 叫做单行查询(single-rowselect) 。2. 如果我们为查询说明一个游标, 就可以 执行查询结果超过 一个元组的查询。 游标将覆盖回答关 系的所有元组, 每个返回的 元组都可 以取到共 享变量 中并由宿 主语言程 序予以处理。我们将依次考察每种机制。7. 1. 5 单行查 询语句除了跟在 SELECT 子句后面的关键字 INT O 和共享变量表以外, 单行查询的形 式与普通的 select-from-where 语句相同。这些共享变量前面都有冒号, 在 SQL 语句中所有的共享变量都是这样的。如果查询的结果是单一的元组, 那么该元组的分量就成为这些变量的 值。如 果 结 果 没 有元 组 或 者 超 过一 个 元 组, 那 么 就 不 会 对共 享 变 量 赋 值, 而 在 变 量SQLST AT E 中写入相应的代码。例 7. 3 我们将写一个C函数来 读取一 个制片公 司的名字 并打印 其总裁的 净资产。该函数的概要如图 7. 3 所示。为了说明我们所需要的变量, 函数以说明段作为开始, 见 1)到 5) 行。接下来, 我们未明确给出的 C 语句将从标准输入设备读入制片公司的名字。再下面, 6) 到 9) 行是单行查询语 句。它和我们已经 看到的查询非常相 似。两者的不同之 处在于: 在 9) 行的条件中, 用变量st udioName代替了 常量字符串, 并在 7) 行有 个INT O子 句·462·
为何使用对象?面向对象的编程为数据库系统提供了几项重要功能:· 利用 丰富的类 型系统, 可以 处理比关 系模型或 更早的 数据模 型更 自然的 数据形式。注意: 关系模型的类型系统多少有些局限。关系就是记录的集合, 而记录结构的域( 关系模型中称为“属性”) 都是基本类型。· 利用 类和类的 分层结构, 可 以比传统 系统更容 易地进 行软件 和数 据模式 的共享或复用。· 利用 抽象数据 类型, 可以限 制对数据 的访问, 从而 防止数 据的 误用, 除非 通过仔细设计的某些函数来访问数据, 已了解这些函数能正确使用数据。1. 3. 2 约束和 触发程序数据库系统的另一个新发展趋势是在商用系统中广泛使用主动性元素。所谓“主动”指的是数据库的组成部分在任何时刻都是准备好了的, 无论何时, 只要时机合适就可以立刻执行。在数据库系统中, 通常有两种主动性元素:1. 约束。约束是布尔型的函数, 要求其值为真。例如, 我们可能在银行数据库中设置一个约束: 结余不能小于 0。DBMS会拒绝不满足该约束的数据库修改操作, 比如, 导致帐户结余为负的提取。2. 触发程序。触发程序是一段等待某事件发生的代 码; 这种可 能的事件可以是 某种数据项的插入或删除等。当事件发生时, 要执行( 也就是要触发) 相关的一系列动作。例如,飞机订票系统可能有这样一种规则, 其触发条件是航班的状态变为“取消”。该规则的动作部分可能是查询, 查找预定了该航班座位的所有顾客的电话号码, 以便通知这些顾客。更复杂的动作也许是自动为这些顾客预订别的航班。主动 性 元 素 并 不 是 什 么 新 的 想 法。 在 程 序 设 计 语 言PL/I中, 它 们 就 以“ON-condit ion”出现了。在人工智能系统中, 它们也已出现了许多年, 而且, 它们在操作系 统中类似于“端口监控程序(daemons) ”。然而, 当主动性元素操作的数据规模很大或主动性元素的数量很多时, 如何有效地实现主动性元素, 存在着严重的技术问题。为此, 直到 90 年代初期, 主动性元素才作为 DBMS 的标准组成部分。我们将在第 6 章讨论主动性元素。1. 3. 3 多媒体 数据数据库系统的另一个重要发展趋势是包容多媒体数据。所谓“多媒体”, 指的是表示某种信号的信息。通常的多媒体数据形式包括各种编码的视频、音频、雷达信号、卫星图象以及文本或图形。这些形式的共同之处在于它们比以往形式的数据 —— 整型、定长字符串等等—— 在容量上大得多, 并且其容量的变化范围也大得多。多媒体数据的存储促使DBMS以 几种方式扩展。比 如, 在多媒 体数据上进行的 操作与适用于传 统数据形式的简 单操作不同。因 此, 虽 然我们可 以通过 比较每个 结余和实 数0. 0 来检索银行数据库中结余 为负的帐户, 但在图象数据 库中, 要检 索某幅脸部像特 定图·31·
告诉 我们 把查 询结果 放到 哪里。在 这种情 况下, 我 们要求 单个 元组, 而元 组 只有 与属 性netWorth对应的一个分量。一个元组的该分量的值将存放在共享变量presNetWorth( 类型为整数) 中。 WvoidprintNetWorth( ) {1) EXEC SQL BEGIN DECLARE SECT ION;2) char studioName[ 15] ;3) int presNetWorth;4)char SQLSTATE[ 6] ;5) EXEC SQL END DECLARE SECTION;/* 打印请求信息, 输入制片公司的名字。将输入的名字放到studioName中 * /6) EXEC SQL SELECT netWorth7) INTO:presNetWorth8) FROM Studio, MovieExec9) WHERE presC# = cert# AND Studio. name = : studioName;/ * 检验 SQLSTATE 是否为全 0, 如果是, 就打印 pr esNetWorth 的值 * /}图 7. 3 嵌入到C函数中的单行查询7. 1. 6 游标把SQL语句连 接到宿主语言 的最通用 的方式 是使用在 一个关 系的各个 元组上移 动的游标(cursor) 。该关系可以是已存储的表, 也可以是查询产生的结果。要建立和使用游标, 我们需要下列语句:1. 游标说明。游标说明的最简单格式组成如下:( a) 用 EXEC SQL 引导, 就像所有嵌入式 SQL 语句那样。(b) 关键字DECLARE。( c) 游标的名字。(d) 关键字CURSOR FOR。( e) h表达式, 例如关系的名字或者 select-from-where 表达 式, 它的 值是一个关系。已说 明 的 游 标 将 覆 盖 该 关 系 的 所 有 元 组; 也 就 是 说, 当 游 标 向 前“ 推 进 ”( fetch) 时, 该游标可以依次指向该关系的每个元组。综上所述, 游标说明的格式为EXEC SQL DECLARE〈游标〉CURSOR FOR〈查询〉2.EXEC SQL OPEN语句, 其 后跟 着 游标 的名 字。该 语句 将游 标初 始 化到 某个 位置, 从该位置可以检索到游标所覆盖的关系的第一个元组。3. 一次或者多次使用推进语句。推进语句的目的是得到该游标所覆盖的关系的下一个元组。如果已经把元组取完了, 那么就没有元组返回, 结果SQLSTAT E的值就设置成 ' 02000' , 该代码意味着“没有找到元组”。推进语句由以下几个部分组成:(a) 关键字EXEC SQL F ET CH FROM。( b) 游标的名字。(c) 关键字INT O。·562·
( d) p由逗号分开的共享 变量表。如果推进到 一个元组, 那么该元组的 各个分量 将依次放到这些变量中。也就是说, 推进语句的格式是:EXEC SQL FET CH FROM〈游标〉INT O〈变量表〉4.EXEC SQL CLOSE语句, 其后跟着游标的名字。该语句关闭现在不再覆盖关系元组的游标。然而, 该游标可以用另一个 OPEN 语句重新初始化, 在这种请况下它将重新覆盖该关系的元组。例 7. 4 假 定我们要 确定电 影行政长 官的净 资产分布 情况, 他们将 按净资产 划分 成按指数增长的若干段, 每段与他们的净资产有几位数相对应。我们将设计一个查询, 它取出 MovieExec 的每个元组的 netWort h 域并且放 到共享变量 wort h 中。游标 execCursor将覆盖这些单一分量的元组。每当取出来一个元组时, 我们就计算在整型变量 wort h 中净资产的位数, 并把数组counts中相应的元素加 1。C 函数 worthRanges 从图 7. 4 的 1) 行开始。2) 行说明了一些只用于 C 函数而不用于嵌入式SQL的变量。数组counts保存不同段内行政长 官的数目,digits计算净资产 有几位数, i 是覆盖数组 count s 的所有元素的下标。3 ) 到 6) 行 是 嵌 入 式SQL的 说 明 段, 在 此 说 明 了 共 享 变 量worth和 常 见 的SQLST AT E。7) 和 8) 行说明 execCursor 为游标, 它将覆盖 8) 行中的查询所产生的值。该查询只是简单地请求MovieExec的所有元组的netWorth分量。然后在 9) 行打开该游标。10) 行通过将数组counts的元素置 0 来完成初始化。 1) void wor thRanges( ) {2)int i,digits,counts[ 15] ;3) EXEC SQL BEGIN DECLARE SECT ION;4)int worth;5)char SQLSTAT E[ 6] ;6)EXEC SQL END DECLARE SECTION;7)EXEC SQL DECLAR E execCursor CURSOR FOR8)SELECT netWorth FROM MovieExec;9)EXEC SQL OPEN execCursor;10) for (i= 0; i< 15; i+ + ) counts[ i] = 0;11) while( 1) {12) EXEC SQL FETCH FROM execCursor INTO : worth;13) if ( NO- MORE- TUPLES) break ;14) digits = 1;15) while( ( worth / = 10) > 0) digits+ + ;16) if( digits < = 14) counts[ digits] ++ ; }17) EXEC SQL CLOSE execCursor;18) for(i= 0;i< 15;i++ )19)printf( ″digits= %d:number of execs= %d\n″,i,counts[i] ) ; }图 7. 4 把行政长官的净资产分成指数段主要的 工 作 是由 11) 到 16) 行 的 循 环 完 成 的。在 12) 行 把 一 个 元 组 取 到 共 享 变 量·662·
wort h 中。因为由 8) 行的查询产生的元组只有一个分量, 所以我们只需要一个共享变量,不过在通常情况下会有多个变量, 而变量数应该和检索到的元组的分量数相同。13) 行检测推进操作是否成功。在这里, 我们使用了宏 NO- MORE- T UPLES, 可以认为它们是由下列语句定义的:# define NO- MORE- T UPLES ! ( strcmp( SQLST AT E, ″02000″) )回忆一下, 当SQLSTAT E的内容是″02000″时意味着没有找到元组。13) 行检测是否查询返回的所有元组前面都已经找到而不存在“下一个”元组了。如果是这样, 我们就跳出循环转到 17) 行。如果取出一个元组, 那么在 14) 行就把净资产的位数 digits 初始化为 1。15) 行是一个循环, 它重复地把净资产除以 10 并把digits加 1。当除以 10 之后净资产为 0 时,digits就保存原来检索到的 worth 值的正确位数。最后, 16) 行将数组 counts 中相应的元素加 1。我们假定净资产的位数不会超过 14。然而, 即使净资产有 15 位或者更多, 16) 行将因为没有相应的作用域而不会把数组 counts 的任何元素加 1; 也就是, 将舍弃巨大的净资产而不会影响统计结果。从 17) 行开始函数进入收尾阶段。关闭游标, 18) 和 19) 行打印数组 counts 中的值。□7. 1. 7 通过游 标的更新当游标覆盖基本表( 也就是数据库中存储的关系, 而不是由查询所构建的视图或者关系 ) 的元 组时, 人 们不 仅 能 读出 并 处理 每 个元 组 的值, 而且 也 能修 改 或 删除 元 组。除 了WHERE 子句之外, 这些 UPDAT E 和 DELETE 语句的句法和我们 在 5. 6 节中遇到 的一样。WHERE子句只能是在WHERE CURRENT OF之后跟着游标的名字。当然, 在决定是否删除或者 修改元组之前, 读取元组的 宿主语言 程序可能 把它喜 欢的任何 条件用于 该元组。例 7. 5 在 图 7. 5 中 我 们 看 到 与 图 7. 4 类 似 的 C 函 数。 二 者 都 说 明 了 游 标execCursor将覆盖MovieExec的元组。然而, 图 7. 5 查找 每个元组并决定是 删除该元 组还是将净资产加倍。我们又把宏NO-MORE-T UPLES用于以 下这种 情况: 变量SQLSTAT E具有表 示“已无元组”的代码″02000″。在 12) 行的测试中, 我们查询净资产是否少于$1 000。如果是这样, 就在 13) 行用删除语句把该元组删 除。如果净资产至 少$1 000, 那么就在 15) 行把净资产加倍。 □7. 1. 8 游标选 项SQL2 为游标提供了各种各样的选项。下面是一个总结。详细介绍可以在 7. 1. 9 节到7. 1. 11 节找到。1. 可以指定从关系中取出元组的顺序。2. 可以限制游标所覆盖的关系发生改变所产生的影响。3. 可以改变游标在元组列表上的移动方式。·762·
1) voi d changeWorth( ) {2) EXE mC SQL BEGIN DECLARE SECTION;3)int wor th;4)char SQLST ATE[ 6] ;5) EXEC SQL END DECLARE SECT ION;6) EXEC SQL DECLARE execCursor CURSOR FOR7)SELECT netWorth FROM MovieExec;8)EXEC SQL OPEN execCursor;9) while( 1) {10) EXEC SQL FETCH FROM execCursor INT O : worth ;11)if(NO- MORE- TUPLES)br eak;12)if(worth< 1000)13) EXEC SQL DELETE FROM MovieExecWHERE CURRENT OF execCursor ;14)else15) EXEC SQLU PDATE MovieExecSET netWorth= 2 *netWorthWHERE CURRENT OF execCursor ;}16)EXEC SQL CLOSE execCursor; }图 7. 5 更新行政长官的净资产7. 1. 9 为取出 的元组排 序让我们先来考虑一下元组的顺序。我们可以把取出的元组按照任一分量的值排序。为了规定 一个顺序, 我们 在游标 所覆盖 的关 系的定 义中 加上关 键字ORDER BY和 用于 排序的分量表, 就像我们在 5. 1. 5 节为查询所做的那样。先按分量表的第一分量排序, 第一个分量相同就按第二个分量排序, 又相同, 再按第三个分量排序, 依次类推。可以用属性或者数字来规定分量。在后一种情况下, 数字指该属性在关系的所有属性中的位置。例 7. 6 假定我们希望检验一个关系的元 组, 该关系 是通过连接和投 影而构成的, 先连接 Movie 和 StartIn 两个 关系, 然后 投影 产生的 关系 只有 电影的 名称、年 份、影 星和 制片公司。我们同样希望按年份对这些关系进行排序, 而在同一年的元组中, 我们将按名称对这些 元组进行排序( 按字母顺序) 。图 7. 6 说 明游标 movieSt arCursor 覆 盖了所构成 的关系。 (1) EXEC SQLDECLARE movieStarCursor CURSOR FOR2) SELECT title, year , studioName, starName3) FROM Movie, StarsIn4)WHERE title=movieTitle AND year=movieYear5)ORDER BY year,title;图 7. 6 用ORDER BY子句控制取出元组的顺序2) 到 4) 行是普通 的SELECT子句, 1) 行 说明 一 个游 标来 覆盖 该关 系 的元 组。5) 行是说当我 们通 过游 标movieSt arCursor取出 元组时, 将 首先 得到 年代 最早 的元 组。同 一·862·
年份的元组则按照第二个属性名称来分组。同一年中的名称将按照字母顺序排序, 因为字符串的值就是这样排序的。此处并没有限定同一部电影中表示不同影星的元组也排序。□7. 1. 10 防止并发 更新的保护 措施下面, 让我们来考虑这种 可能性: 当某个函数通过 例 7. 6 的movieStarCursor游标 来读 取元 组 时, 并发 执行 的某 个函 数 ( 或 者甚 至 是同 一函 数) 正 在 改变 底 层 的 Movie 或 者StartsIn关系。在 7. 2 节, 我们将更多地介绍几个进程同时访问一个数据库的情况。然而,现在, 让我们只是接受这种可能性, 即当我们使用一个关系时其他进程可能更新它。对于这种可能性我们能做什么呢? 也许什么也做不了。我们可能只是为了某个或者某些影星而检索元组, 至于影星所在的元组是 否正在插入或者删 除并不重要。于是, 我们只是简单地接受通过游标得到的元组。然而, 我们也许不希望由于并发产生的变化影响我们通过游标查看的元组。例如, 如果我们的函数查看的元组导致函数把新的元组增加到St arsIn, 我们 就可能会进入一 个反馈环, 也就是新的元组通过游标产生附加的元组, 结果又产生更多的新元组, 迅速激增。如果 有 这 种 或 那 种 不 希 望 发 生 的 危 险, 我 们 不 妨 说 明 游 标 对 并 发 产 生 的 变 化 不 敏 感( insensitive) 。例 7. 7 我们可以把图 7. 6 中的 1) 行改为:1) EXEC SQL DECLARE movieStarCursor INSENSIT IVE CURSOR FOR如果这 样说明movieStarCursor, 那么SQL系统将 确保在 打开和 关闭 游标之 间关 系Movie 或 StarsIn 发生的变化将不会影响取出元组的集合。 □考虑到SQL系统也许要花费更多时间来管理对数据的访问以确保游标不受影响, 因此不敏感的游标可能开销较大。另外, 对数据库中管理并发操作的讨论将推后到 7. 2 节。然而, 一个支持不敏感游 标的简单 方法是, 让SQL系统把可 能访问 使用不敏 感游标的 底层关系( 比如 Movie 或 StarsIn) 的任何进程挂起。还有一些覆 盖在关系R上的 游标, 我 们可以肯定地说它 们将不会改变关 系R。这 种游标可以和 R 的不敏感 游标同 时运行, 而不会 发生不 敏感游标 看到关 系 R 改变 的危险。如果我们说明 一种FOR READ ONLY的游 标, 那 么数据库 系统就 会由于是 通过这种 游标访问数据库而确保底层的关系不会改变。例 7. 8 我们可以在图 7. 6 的 5) 行后面加上 6) 行6) FOR READ ONLY;如 果 我 们 这 样 做 了, 那 么 通 过 游 标movieStarCursor来 执 行UPDAT E或 者DELET E 的任何尝试都会发生错误。 □7. 1. 11 滚动游标游标选项的最后一类是选择如何在关系的元组上移动。默认的也是最常见的选择是从头开始, 依次取出元组, 直到结束。然而, 还可按其他次序取出元组, 并在关闭游标之前,对元组多次扫描。为了利用这些选项, 我们需要做两件事情。·962·
1. 在说 明游 标时, 把 关 键字 SCROLL 放 在关 键 字 CURSOR 之 前。这 种 改变 告 诉SQL系统, 游标可能不以按元组顺序向前移动的方式使用。2. 在推进语句中, 在关键字 FET CH 之后跟 上几种选项之一来 告诉到哪里找到 想要的元组。这些选项是: ( a) jNEXT 或者 PRIOR 将依次取下一个或者上一个元组。 记住这些元组以 游标的当前位置为基准。如果没有指定选项,NEXT就是默认的选择, 而且也是通常的选择。 (b) rFIRST或者LAST将依次取第一个或者最后一个元组。 ( c) u跟着 一个正 整数 或者 负整数 的 RELAT IVE ( 相对) 表 示依次 向下 ( 如 果是 正整 数 ) 移 动 多 少 元 组 或 者 向 上 ( 如 果 是 负 整 数 ) 移 动 多 少 元 组。 例 如,RELAT IVE 1 是 NEXT 的同义词, 而 RELAT IVE - 1 是 PRIOR 的同义词。 (d) z跟着一个 正整数或 者负整 数的ABSOLUTE( 绝对) , 表 示从前面 ( 如 果正) 或者从 后面( 如 果负) 计算的 所要元 组的位置。例 如, ABSOLUT E 1 是 FIRST的同义词, 而ABSOLUT E- 1 是LAST的同义词。例 7. 9 让我们重写图 7. 5 中的函数, 从最后一个元 组开始, 通 过元组的列表向 回移动。 首 先, 我们 需 要 说明 游 标execCursor是 滚动 型 的, 这 就需 要 在 6) 行 中加 上 关 键 字SCROLL 如下: 6)EXECSQL DECLARE execCursor SCROLL CURSOR F OR 7) SELECT netWorth FROM MovieExec;此外, 我们还需要用FET CH LAST语句把取出元组的操作初始化, 并在循环中 使用FETCH PRIOR。把图 7. 5 中 9) 到 15) 行的循环重写如下:EXEC SQL F ETCH LAST FROM execCursor INT O:worth;while( 1) { / * 与 11) 到 15) 行相同 * / EXEC SQL F ETCH PRIOR FROM execCursor INT O : worth;}读者不应该认为 SELECT netWort h FROM MovieExec 产生的倒序的元组有什么好处。事实上, 让系统提供反向的元组开销会更大, 因为在游标execCursor推进到第一个位置之前就不得不把全部元组生成并存储起来。 □7. 1. 12 动态SQL我们嵌入到宿主语言中的SQL模型都是较大的宿主语言程序中特定的SQL查 询和命令。然而有一种更通用的模型可以把 SQL 嵌入到另外一种语言中。语句本身可以由宿主语言所计算。这样的语句在编译的时候并不知道, 所以不能由SQL预处理程序或 者宿主语言编译程序来处理。这种情况的例子是这样一个程序: 提示用户为SQL查询输入信息, 然后读入查询, 再执行该查询。在第 5 章中我们所假设的特定 SQL 查询的解释程序就是这种程序的一个例子; 每个商业的SQL系统都提供这类解释程序。如果在运行时读入并执行查询要求, 那么·072·
在编译时就没什么可做 的了。SQL 系统 读入查询要求之 后, 就立即 进行分析并以合 适的方式予以执行。宿主语言程序 必须指导 SQL 系 统处理读 入的字 符串, 把它转换 成可执 行的 SQL 语句, 并且最终执行该语句。这里有两个动态的SQL语句来执行这两个步骤。1. EXEC SQL PREPARE, 后跟 SQL 变量 V、关键字 FROM 以及字符串类型的宿主语言变量或表达式。这个动态的SQL语句使字符串变成一个SQL语句, 而V的值就变成该SQL语句。可以推测,SQL系统将对SQL语句进行语法分析, 并且找到执行该语句的好方法, 但是还没有执行该语句。2.EXEC SQL EXECUT E, 后跟一个SQL变量如步骤 1 中的V。该语句的作用是执行V所代表的SQL语句。通过在如下语句EXEC SQL EXECUT E IMMEDIAT E后面跟上字符串型的共享变量或者字符串型的表达式, 这两个步骤可以合并成一个步骤。在一个语句作一次预处理, 然后执行多次的情况下, 就可以看出把这两部分合起来的缺点了。若 用EXECUT E IMMEDIAT E这 种形 式, 每当执 行该 语句 时都要 花费 预处 理的 时间, 不如只预处理一次。例 7. 10 图 7. 7 中给出 一个C程 序的 概要, 它从 标准 输入 设备 把文 本 读入 到变 量query 中, 进行预处理并且执行它。SQL 变量 SQLquery 保存预处理过的查询语句。因为这样的查询只执行一次, 用下面这样一个语句来代替图 7. 7 中的 6) 和 7) 行是可以接受的:EXEC SQL EXECUT E IMMEDIATE : query; □ (1) void readQuery( ) {2) EXEC SQL BEGIN DECLARE SECTION;3) char * query;4)EXEC SQL END DECLARE SECTION;5) / * E提示用户输入一个查询, 分配空间( 例如, 使用malloc)并且使共享变量:query指向该查询的第一个字* /6)EXEC SQL PREPARE SQLquery FROM:query;7) EXEC SQL EXECUTE SQLquery; }图 7. 7 预处理并执行动态SQL查询7. 1. 13 本节练习练习 7. 1. 1: 基于练习 4. 1. 1 中的数据库模式, 写出下列嵌入式的SQL查询。Product( maker, model, type)PC(model,speed,ram,hd,cd,price)Laptop( model, speed, ram, hd, screen, price)Print er(model,color,type,price)你可以使用任何一种你熟悉的宿主语言, 如果愿意, 你可以用清晰的注释来代替宿主语言程序的具体细节。·172·
* ( a) 询问用户所要求的价格( price) , 找出与要求的价格最接近的 PC。打印 PC 的厂商(maker) 、型号(model) 和速度(speed) 。 ( b) 询问用 户所能接 受的速 度( speed) 、内 存( RAM ) 、硬盘 容量( hd) 和屏幕尺 寸(screen) 的最小值。查找所有满足以上要求的便携式电脑(lapt op) 。打印它们的规格( lapt op 的所有属性) 和它们的厂商。! (c) 要求用户指定一个厂商。打印该厂商的所有产品(product) 的规格。也就是,打印型号( model) 、类型( type) 和适合该类型的任何关系的所有属性。! ! (d) 要求用户给出预算(“budget”) ( 指的是一 台PC和一台 打印机的总价) , 以及PC 的最低的速度( speed) 。找出 在预 算和最 低速 度范 围内最 便宜 的“系统”(PC加 上打印机) , 但是如 果可能就使打印 机是一台 彩色打印 机。打印所 选择系统的型号( model) 。 (e) 要 求 用 户 给 出 一 台 新PC的 厂 商、型 号、速 度、内 存、硬 盘 容 量, 光 驱 速 度( CD) 和价格。检验是否有这种型号的 PC。如果有就打印一个通知信息, 否则将信息插入到Product和PC表中。* ! ( f) 使 所有“旧”PC 的 价格调低 $100。要确 保在你 的程序运 行期间插 入的任 何“新”PC的价格不会调低。练习 7. 1. 2: 基于练习 4. 1. 3 中的数据库模式, 写出下列嵌入式的 SQL 查询。Classes(class,type,country,numGuns,bore,displacement)Ships( name, class, launched)Battles(name,date)Outcomes( ship, battle, result )(a) !舰艇的火力大致和火炮 的数 量(numGuns) 与 火炮 口径(bore) 的 立方之 积成 正比。找出具有最大火力的舰艇等级( class) 。! (b) 0要求用户给出一次战役(batt le) 的名称(name) 。找出参加该战役的舰艇所属的国家。打印沉没舰艇最多的国家和损坏舰艇最多的国家。(c) 要求用户给出一个等级的名字和表Classes的一个元组所需要的其他信息。接下来 查询那种等级的 舰艇的名字的列表 和它们的下水( launched) 日期。然而,用户不必给出全称中的第一个名字, 因为它必须是该等级的名字。! ( d) *检验 Battles, Outcomes 和 Ships 关系以找出在下水之前就参加战役的舰艇。如果发现错误, 就提示 用户, 在 改变下水 日期或 者战役日 期之间提 供选择。做 所请求的任何一种改变。* ! 练习 7. 1. 3: 在本练习中, 找出如下关系中满足条件的所有PC。PC ( model, speed, ram, hd, cd, price)条件是: 至少有两种速度相同而价格更贵的PC。尽管我们有许多办法来解决该问题, 但是在该练习中读者应该使用滚动游标。读取先按速度( speed) 再按价格( price) 排序的 PC 的元组。提示: 对于每个读出的元组, 往前跳两个元组看看速度是否没变。! ! 练习 7. 1. 4: 在 7. 1. 1 节中, 我 们提 到用 SQL 不能 写出 求 阶乘 的 程 序。那 种断 言 对SQL2 是正确的。然而, 就像在 5. 10 节所讲的SQL3 递归允许我们做一些近似的事情。写·272·
出用于关系 M 的递归 SQL3 查询, M 包含单个元组( m) , 其中 m 是一个整数。查询的结果将是元组的集合(n,n! ) , 其中 1≤n≤m。7. 2 SQL 中的事务到目前为止, 我们对数据 库进行操作的 模型都 是一个用 户对数 据库进行 查询或者 更新。因此, 在某个时刻对数据库只执行一个操作, 而前一个操作遗留的数据库状态就是下一个操作所处的状态。此外, 我们假定操作作为一个整体都执行了; 也就是, 不可能出现如下情况: 在一个操作的执行过程中由于硬件或者软件的故障, 而使数据库处于在数据库正常操作下无法解释的状态。真正的情况通常要复杂得多。我们将首先考虑什么样的操作使数据库处于不反映在它上面所执行 的操作的状态, 然后, 我们 将考虑SQL提供给 读者什 么样的工 具以保证 这些问题不会发生。7. 2. 1 可串行 性在像银行业和飞机订票的应用中, 每秒钟都会对数据库执行数以百计的操作。这些操作可能在成百 甚至成千的节点 中的任何一个 上开始执 行, 例 如在自 动出纳机 或者旅游 代理、航班职员或航班顾客自己的台式机上执行。因为操作时间上的重叠, 两个操作完全有可能同时作 用于同一帐户或 者同一航班。如 果这样, 它们可 能以非 常奇怪的 方式互相 影响。这里有一个例子说明, 如果数据库管理系统对在数据库上的操作顺序完全没有限制可能会发生什么样的错误。我们强调数据库系统在正常情况下不是按照这种方式工作的, 当使用商业数据库管理系统时, 不得不想尽办法以避免发生这类错误。例 7. 11 假定我们编写 了函数 chooseSeat( ) 来读取 关于航班和空座 位的关系, 找出是否还有特定的座位空着, 如果有, 就 把它置成非空。我 们操作的关系称 为Flights, 其属性有 fltNum, fltDate, fltSeat 和 occupied, 这些 属性都具有很明显 的意义。座位选择 程序的概要如图 7. 8 所示。图 7. 8 中 9) 到 11) 行是一个 单行查询, 它根据 指定的座 位是否 已占用设 置共享变 量occ为 真或假( 1 或 0) 。12) 行 检验座位是否已 占用, 如果 没有, 就 把该座位的 元组修改 为占用。修改操作由 13) 到 15) 行完成, 在 16) 行将把座位分配给需要的用户。实际上, 我们可以把座位分 配 信息 存 放在 另 一个 关 系 中。最后 在 17) 行, 如 果 座位 已 占用, 就要 通 知用户。现在, 请记住, 两个或者更多的用户可能同时执行函数chooseSeat( ) 。假定碰巧 两个代理几乎在同一时间试图预定同一天、同一航班的同一座位, 如图 7. 9 所示。在同一时间它们都执行到 9) 行, 并且它们的本地变量occ的副本都取值 为 0; 也就是, 座位尚未分配。从 12) 行起 chooseSeat ( ) 的每个执行都将此位修改为 1, 也就是, 占用该座位。这些操作可能一个接一个地执行, 每个执行都在 16) 行告诉用户“该座位属于你”。 □如我们从例 7. 11 中看到的那样, 两个操作各自执行都正确, 但整体的结果却不正确,这种情况是可能的: 两个用户都认为自己请求的座位已落实。该问题可以这样解决: 通过·372·
保证可串行化特性实际上, 要求操作串行 运行往往 是不可 能的; 因为操作 太多, 需要一 定的并行 性。于是数据库管 理系统就采取一 定机制来保 证可串 行化的特 征; 即 使执行 不是串行 的,但是对用户来说, 结果看起来好像操作是串行执行的。像我们在 1. 2. 4 节所讨论的那样, 一个通用的方法是数据库管理系统锁住数据库的元素从而两个函数不能同时访问这些元素。例如, 若把例 7. 11 的函数chooseSeat( )写成锁住关系 Flights 的其他操作, 则不访问 Flights 的操作就可以和 chooseSeat( ) 的该请求并行运行, 但是chooseSeat( ) 的另一请求不能运行。实际上, 像 1. 2. 4 节中提到的那样, 锁住比整个关系更小的元素, 例如单个磁盘块或者单个元组, 将允许更多的并行性, 包括同时运行chooseSeat( ) 的某些请求的能力。 1) EXECSQL BEGIN DECLARE SECTION;2) int flight; / * 航班号 * /3)char date[ 10] ; / * 用SQL格式表示的航班日期 * /4) char seat[ 3] ; / * 两个数字和一个字母表示一个座位 * /5)int occ; / * 用布尔值表明座位是否已占用 * /6) EXEC SQL END DECLARE SECTION;7) voidchooseSeat( ) {8) / * C 代码提示用户输入航班号、日期和座位, 并把它们保存在具有相应名字的三个变量中 * /9) EXEC SQL SELECT occupied INTO : occ10)FROM Flights11) WHER E fltNum = : flight AND fltDate = : dateAND fltSeat = : seat;12) if( !occ) {13) EXEC SQL UPDAT E Flights14)SET occupied= ‘B1'15) WHE RE fltNum = : flightAND fltDate= :dateAND fltSeat = : seat;16) / *C和SQL代码登记座位分配情况并通知用户分配结果 * / }17) else/ *C代码通知用户座位已占用并请求另选座位 * / }图 7. 8 选择座位几个SQL机制使两个函数的执行实现串行化。如果在一个函数开始执行以前另一个函数已经执行完了, 我们就说对同一数据库进行操作的两个函数其执行是串行的。即使两个函数的执行在时间上可能是重叠的, 只要它们执行的情况如同是在串行执行, 我们就说这种执行是可串行化的( serializable) 。很明显, 如果chooseSeat( ) 的两个请求是串行( 或者可串行化地 ) 运行 的, 我们所 看到的错误就不会发生。一个用户首先发出请求。该用户看到一个空座位并把它预订了。然·472·
象的图, 用这种方法是 行不通的。于是, DBMS 必须为 用户提供 一种能 力, 允 许用户引 入他们自己选择的、可以应用在多媒体数据上的函数。通常, 用面向对象的方法进行这种扩展, 即使在关系系统中也是如此。多媒体对象的容量也促使DBMS修改存储管理程序, 以适应G字节或更大的对象或元组。容量如此大的元素如何表示, 存在很多问题, 其中包括查询结果的传输问题。在传统的关系数据库中, 查询结果是元组的集合, 可以作为一个整体由数据库服务程序传输给客户程序。然而, 如果查询的结果 是G字节长的 一段视 频剪辑, 服务程 序要把G字 节作为一 个整体传给客 户程序是不可能 的。一个原因是 时间太长, 将会 妨碍服 务程序处 理其他的 请求。另一个原因是, 客户程序可能只想要电影剪辑的一小部分, 但在没有看到剪辑的起始部分之前, 无法确定究竟想要哪一部分。第三个原因, 即使客户程序想要整个剪辑, 或许是为了在屏幕上播放, 以 固定的速率用一小 时传输已经足够 了( 一 小时是播放 1G字节 的压缩视频所需的时间) 。因此, 多媒体 DBMS 的存储系统必 须准备好以交互 方式传输结果,传送客户程序请求的片断或以固定的速率进行传送。1. 3. 4 数据集 成由于信息在我 们的工作和娱乐 中变得越 来越重要, 我们 发现人 们正在以 许多新的 方式利用现有的信息资源。例如, 设想有一个公司想为它的所有产品提供联机目录, 使人们能利用万维网浏览它的产品并发出联机订单。大公司往往有很多部门, 每个部门可能已经各自独立地 建立了自己的产 品数据库。这些部 门可能使 用不同的DBMS、不 同的信息 结构, 甚至用不同的术语表示同一事物, 或者用同一术语表示不同的事物。例 1. 4 假设某磁盘制造公司由几个部门 组成。一个部门的产 品目录以每秒的 转数来表示旋转速率, 另一个则以每分钟的转数来表示旋转速率, 而还有一个部门根本就忽略了旋转速度。生产软盘的部门可能将软盘称为“磁盘”; 而生产硬盘的部门也可能称硬盘为“磁盘”。磁盘上的磁 道 数在 一个 部门 可能 叫 做“磁道 数”, 在另 一 个部 门可 能叫 做“柱 面数”。 □集中控制并不总是解决的办法。在各部门意识到它们之间的完整性是一个问题之前,可能已经在各自的数据库上投入了大量的资金。还有的部门可能由于需要最近已经成了独立的公司。由于这种或那种原因, 并不那么容易淘汰这些所谓的“遗留数据库”。因此,公 司 必 须 在 遗 留 数 据 库 的 上 层 建 立 某 种 结 构, 以 便 给 顾 客 提 供 整 个 公 司 统 一 的 视 图( view) 。一 种流 行的方 法是 建立数 据仓 库(data warehouse) , 即 许多 遗留 数据库 中的 信息 通过适当的转换复制到中央数据库。当遗留数据库变更的时候, 数据仓库也跟着更新, 但不必立即更新。通常的安排是每天晚上遗留数据库不太忙的时候重构数据仓库。这样, 遗留数据库 就能继续 用于当 初建立的 场合, 而诸如 通过 Web 网提 供联机产 品目录服务等 新的功能则在数 据仓库上完成。 我们看到, 数据 仓库还 符合规划 和分析的 需要。例如, 公司分析员可以在数据仓库上进行查找销售趋势的查询, 以便更好地规划库存和生产。数据仓库的构造也支持数据挖掘, 即在数据中寻找感兴趣的、不寻常的模式, 并且·41·
后另外一个用户的请求开始执行并且看到座位已经占用了。对于用户来说谁得到座位是有关系的, 但是对数据库来说, 重要的是一个座位只能分配一次。用户 1 发现座 位空时间 用户 2 发 现座位 空↓用户 1 占用该 座位用户 2 占 用该座 位图 7. 9 两个用户试图同时预订同一座位7. 2. 2 原子性除了两个或者 更多的数据库操 作同时执 行可能引 发非串行 化行为 以外, 当单个操 作正在执行 时如果出现硬件 或者软件的“崩溃”, 那么 该操作也 可能把 数据库置 于不可接 受的状态。这里有另外一个例子提醒我们可能会发生什么。如例 7. 11 所示, 我们应当记住实际的数据库系统不允许在设计合理的应用程序中发生这种错误。例 7. 12 让我们来设想另一种常见的数据库类型: 银行的帐 户记录。我们可以 用具有属性acctNo和balance的关系Accounts来表示这种情形。在该关系中帐户号和该帐户的余额组成一对。我们希望写一 个函数 t ransfer( ) , 它读取 两个帐户和一定 数量的钱, 检验 第一个帐 户是否至少有那么多钱, 如果是, 就把这些钱从第一个帐户转到第二个帐户。图 7. 10 是函数transfer( ) 的概要。 1) EXECSQL BEGIN DECLARE SECT ION;2) int acct1, acct2; / * 两个帐户 * /3)int balance1; / * 第一个帐户的结余* /4) int amount; / * 转帐的金额 * /5) EXEC SQL END DECLARE SECT ION;6) void transfer( ) {7) / * C 代码。提示用户输入帐户 1 和帐户 2 以及转帐的金额, k放到变量acct1,acct2 和amount中 * /8) kEXE &C SQL SELECT balance INTO : balance19)FROM Accounts10) WHERE acctNo =∶ acct1;11)if(baJlance1 > =amount) {12) JEXEC SQL U PDATE Accounts13)SET balance=balance+∶amount14)WHERE acctNo=∶acct2;15) EXEC SQL U PDATE Accounts16)SET balance=balance- ∶amount17) WHERE acctNo =∶ acct1; }18) else / * C 代码。如果没有足够的钱进行转帐, 就打印一个信息。* /}图 7. 10 从一个帐户向另一个帐户转帐·572·
在事务处理过程中数据库是如何改变的不同的系统可能以不同的方式来实现事务。当事务执行时, 它可以改变数据库。如果该事务失败了, 某个其他事务可能看到那些改变。最常用的解决办法是让数据库系统锁住变化的数据项直到选择COMMIT( 提交) 或ROLLBACK( 退回) 为止。这样可以避免其他事务看到这种暂时的改变。如果用户想让事务以可串行化的方式运行, 那么一定要使用锁或者等效的东西。然而, 就像我 们从 7. 2. 4 节开始 看到 的那样, SQL2 提供给 我们 几个 选项来 处理暂时的数据库改变。即使随后的退回使改变不可见, 改变的数据也可能没有锁住而变得可见。事务的设计者要决定是否避免暂时改变的可见性。如果是这样, 那么所有的SQL实现将提供像加锁这样的方法来保持在提交之前的改变是不可见的。图 7. 10 的处理过程简单明了。8) 到 10) 行检索第一个帐户的余额。在 11) 行确定余额是否足够多从而可以从中减去所要求的数量。如果是这样, 那么 12) 到 14) 行就把该数加到第二个帐户上, 而 15) 到 17) 行则从第一个帐户减去该数。如果第一个帐户的钱不够,那么就不做转帐, 并在 18) 行打印通知信息。现在考虑, 如果在 14) 行后面发生了 故障; 也 许是计算机故障或 者是实际执行转 帐的连接 数据库 和处 理机的 网络 故障, 那么 数据 库就 将处于 这种 状态: 钱已 经转 到第 二个 帐户, 但是还没有从第一个帐户取走。银行实际上损失了要转帐的这笔钱。 □例 7. 12 说明的问题是数据库操作的某些组合( 如图 7. 10 中的两个修改) 需要以原子的方式完成, 也就是, 它们或者都做, 或者都不做。例如一个通常的解决方法是让对数据库的所有改变在 本地的工作区完 成, 并且只 有当所有 的工作完 成以后 我们才把 这种改变 提交给数据库, 于是所有的改变都成为数据库的一部分并且对其他操作是可见的。7. 2. 3 事务7. 2. 1 和 7. 2. 2 节中提出的 串行化和原子性 问题的解决方法是 把这些数据库操 作组合成事务( transaction) 。事务是在数据库上的一个或者多个操作的聚集, 它必须以原子的方式执行; 也就是, 所有的操作要么都做, 要么都不做。另外, 早期的SQL标准还要求事务好像以串行的方式 执行; 也就 是, 它们 是可串行化的。然 而, SQL2 具有更 加灵活的观点。在SQL2 中, 可串行性是默认的,①但是用户可以在两个或者更多的交叉事务中规定稍加严格的限制。在以后的几节中我们将讨论这些对可串行性条件的更改。当对数据库或者模式进行查询或操作的任何SQL语句开始时, 事务也就开始 了。在SQL 中我们不必给出任何专门的事务开始语句。然而, 我们必须明确地结束一个事务。我们可以用两种方法来结束事务。1. SQL 语句 COMMIT ( 提交) 使事务成功地结束。自从当前事务开始后 SQL 语句所造成的 数据库的任何改 变将永久地放置 在数据库中 ( 也 就是, 把改变 的内容提 交了) 。在·672·①尽 管某些 实现 对此稍 加限制 。
COMMIT 语句执行以前, 改变都是暂时的, 对其他事务可能可见也可能不可见。2.SQL语句ROLLBACK( 退回) 使事务异常终止, 即不成功 地终止。响应该事 务的SQL语句所造 成的任 何改变都 作废了 ( 也就 是, 把 改变的 内容 复原 了) , 所 以数据 库不 会发生改变。例 7. 13 假定我们想让图 7. 10 中的函数transfer( ) 以单个事务的形式执行。在 8) 行当我们读取第一个帐户的余额时, 事务开始。 如果 11) 行 的测试为真, 而且完成了资 金的转帐, 那么就要提交所做的改变。所以我们把附加的 SQL 语句EXEC SQL COMMIT ;放在 12) 到 17) 行的if子句的后面。如果 11) 行的 测试为假 —— 也就 是, 没有足 够的 资金 来转帐 —— 我们 宁愿取 消该 事务。通过把语句EXEC SQL ROLLBACK;放在 18) 行所示的else子句后面就可以做到这一点。实际上, 既然在该分 支中, 没有 执行数据库更新语句, 是提交还是异常终止都没有关系, 因为没有什么变化需要提交。 □7. 2. 4 只读事 务例 7. 11 和例 7. 12 各自包含一个事务, 先读然后( 可能) 写一些数据到数据库中。这类事务容易引发串行化问题。于是我们在例 7. 11 中看到如果函数的两个执行都想在同一时间订同一座位会发生什么情况, 同时我们在例 7. 12 中看到如果在函数执行过程中发生崩溃会发生什么情况。然而, 当一个事务只是读数据而不写数据时, 我们就有更大的灵活性让该事务和其他事务并行执行。①例 7. 14 假定我们写了一个函数来读取数据以确定 是否还有特定的 座位空着; 该函数就像图 7. 8 中 1) 到 11) 行那样运行。我们能够同时执行对该函数的许多请求, 而不用担心对数据库造成永久的损害。可能发生的最坏的情况是, 当我们读取一个特定的座位判断其是否空着时, 其他某个函数执行正在预订或者正在退掉该座位。这样, 我们可能会得到“空着”或者“不空”的回答, 这取决于我们执行查询时在时间上的细微差别, 但是这种答案有时是有意义的。如果我们告诉SQL执行系 统当前的 事务是 只读的, 也就是 它绝对 不会改变 数据库,那么 SQL 系统很可能会利用该信息。尽管我们在此不讨论详细的机制, 然而让许多 访问同样数据的只 读事务并行运行 一般来说是可 能的, 但是不允 许它们 和写相同 数据的事 务并行运行。我们用SET T RANSACT ION READ ONLY;来告诉SQL系统下一个事务是只读的。该语句必须在事务开始之前执行。例如, 如果我们有一个由图 7. 8 中的 1) 到 11) 行组成的函数, 就可以把·772·①可 在事务 和游 标的管 理之 间进行 比较 。例 如, 我们 在 7. 1. 10 节 提到只 读游标 比一 般游标 具有 更多的 并行 性。类似地 , 只 读的 事务允 许并 行操作 。
EXEC SQL SET T RANSACT ION READ ONLY;刚 好 放在 事务 开始 执 行的 9) 行 之前 来说 明 它是 只 读 的。在 9) 行 之 后 做只 读 说明 就 太晚了。我们同样可以用如下语句SET T RANSACT ION READ WRIT E;来通知 SQL 系统接下来的事务将要写或者可能写数据。然而, 该选项一般是默认的, 因此也是不必要的。 □7. 2. 5 读脏数 据脏 数据 是个常 用术 语, 用来表 示已 由事务 写完 但尚 未提交 的数 据。读脏 数 据( dirtyread) 就是对脏数据的读取。读脏数据的危险是写脏数据的事务最终可能异常终止。如果这样, 那么将把脏数据从 数据库中清除出 去, 一切都好 像那些数 据从来 没有存在 过一样。如果其他某个 事务读取了脏数 据, 那么该 事务可能 提交或者 用其他 某种操作 来反映脏 数据的内容。读脏数据有时有问题, 有时没问题。在影响足够小的情况下, 值得冒险来避免数据库管理系统为防止读脏数据而进行耗费时间的必要操作。这里有一些例子说明当允许读脏数据时会发生什么事。例 7. 15 让我们重新考虑例 7. 12 中的帐户转帐。然而, 假定转帐是通过执行下面一系列步骤的程序P实现的:1. 把钱加到帐户 2。2. 检验帐户 1 是否有足够的钱。( a) 如果没有足够的钱, 从帐户 2 去掉这笔钱并且异常终止。(b) 如果有足够的钱, 从帐户 1 减掉这笔钱并且提交。如果程序P可串 行化执行, 那么我们暂时 把钱放入帐户 2 就 不会有问题。没有 人会看到这笔钱, 而且如果不能转帐就会把这笔钱去掉。然而, 假设可能读脏数据。假定有三个帐户:A1,A2 和A3 分别存有$100, $200 和$300。假定事务 T 1 执行程序 P 从 A1 到 A2 转帐$150。几乎同时, 事务 T 2 执行程序 P从A2 到A3 转帐 $250。可能的事件顺序如下:1. T2执行步骤 1, 往 A3 加$250, A3 现在有$550。2. T 1 执行步骤 1, 往 A2 加$150, A2 现在有$350。3.T2 执 行步 骤 2 的 检测, 发 现A2 具 有足 够的 资金 ( $350) 用 于从A2 到A3 转 帐$250。4.T1 执 行步 骤 2 的 检测, 发 现A1 没 有足 够的 资金 ( $100) 用 于从A1 到A2 转 帐$150。5. T 2 执行步骤 2( b) , 从 A2 减$250, A2 现在有$100, 于是 T 2 提交。6.T1 执行步骤 2(a) , 从A2 减$150,A2 现在有- $50, 于是T1 异常终止。总钱数没有变, 在三个帐户中仍然有$600。但是因为事务 T 2 在以上 6 个步骤中的第·872·
三步读取了脏数据, 因此我们没能防止一个帐户变负, 这恐怕就是检测第一个帐户看它是否有足够资金的目的。 □例 7. 16 让我们假想例 7. 11 中 seat-choosing 函数的变形。在新的方法中:1. 我们找到一个空座位, 通过把该座位的occupied置成 1 来预订它。如果没有座位,就异常终止。2. 我 们询问顾 客是否 同意该座 位。如果同 意, 我 们就提 交。否则, 我 们就通 过设 置occupied为 0 释放该座位, 然后重复步骤 1 去找另一个座位。如果 两个事 务几 乎同时 执 行该 算法, 一 个事 务 可能 预订 座位S, 而过 后 顾客 又退 掉了。如果第二个事务在座位 S 标记为已占用的时刻执行了步骤 1, 那么该事务的顾客对座位S将没有选择的机会。像例 7. 15 那样, 读脏数据的问题发生了。第二个事务看到一个元组( 其中座位 S 标记为已占用) 由第一个事务写过然后又由第一个事务修改了。 □读脏数据到底有多大影响呢?在例 7. 15 中, 读脏数据会造成严重影响; 尽管显而易见地想防止发生意外, 但是读脏数据还是使一个帐户变成了负的。在例 7. 16 中, 问题看起来没有那么严重。的确, 第二个旅行者没有得到他( 她) 想要的座位, 或者甚至被告知没有座位了。然而, 在后一种情况 下, 再次 执行该事务时几 乎可以肯定会显 示出座位S是空的。为了减少订票请求的平均处理时间, 以允许读 脏数据的方式来 实现 seat-choosing 函 数是有意义的。SQL2 允许我们对给定的 事务规定可以读 取脏数据。我们将使 用在 7. 2. 4 节讨 论的SET T RANSACTION语句。像在例 7. 16 中描述的事务的合适形式是:1) SET T RANSACTION 7READ WRIT E2)ISOLATION LEVEL READ UNCOMMITT ED;上面的语句做两件事情:1. 1) 行说明了事务同时读和写数据。2. 2) 行说 明 了 下 列事 务 可 以 用 隔离 性 级 别 ( isolation level) 为 读-不 提交 的 方 式 运行。在 7. 2. 6 节我们将讨论 4 种隔离性级别。迄今为止, 我们看到了其中的两种: 可串行化和读-不提交。注意: 如果事务不是 只读的( 也就是, 它至少往 数据库 中写入一 个数据项 ) , 而且我 们规定 隔离 性级 别为 READ U NCOMMIT T ED, 那 么 我们 必须 同时 规定 READ WRIT E。回想一下在 7. 2. 4 节中默认 的假定为事务是 读-写的。然 而,SQL2 把允许读 脏数据的 情况作为一个异常。于是, 默认的假定为事务是只读的, 因为就像我们看到的那样, 读脏数据时读-写事务要冒很大的风险。如果我们想让读-写事务以读-不提交的方式作为隔离性级别来运行, 那么我们就要像上面那样, 明确规定 READ WRIT E。7. 2. 6 其他隔 离性级别SQL2 总共提供了 4 种隔离性级别。我 们已经看到了 其中的 两种: 可串行化 和读-不提交( 允许读脏数据) 。其他两种是读-提交和可重复读。对于给定的事务可如下规定这两种级别·972·
SET T RANSACTION ISOLATION LEVEL READ COMMIT T ED;或者 SET T RANSACTION ISOLATION LEVEL REPEAT ABLE READ;对 于 每 种 情 况, 都 默 认 事 务 是 读-写 的, 所 以 在 适 当 的 时 候, 我 们 可 以 给 每 个 语 句 增 加READ ONLY。顺便提一下, 我们也可以规定下列选项: SET T RANSACTION ISOLATION LEVEL SERIALIZABLE;然而, 这是 SQL2 的默认设置, 不必明确指出。读-提交隔离性级别, 就像它的名字所隐含的那样, 禁止读脏的( 没有提交的) 数据。然而, 它确实允许一个事务发出好几次同样的请求而得到不同的结果, 只要结果反映的是已经提交的事务所写的数据就可以。例 7. 17 让我们再来考虑例 7. 16 中的 seat-choosing 函数, 但是假定函数是在读-提交的隔离性级别上运行的。于是, 当函数在第一步寻找座位时, 如果某个其他事务正在预订但是还未 提交, 那么, 它就看 不到预订的座位。①然 而, 如果旅 客拒绝预订的座 位, 并 且该函数的一个 执行过程可多次 查询空座位, 那么当 其他与该 事务并 行的事务 成功地预 订到座位或者退掉预订的座位时, 每次查询该函数将会看到不同的空座位集合。 □现在, 让我们考虑可重复读这种隔离性级别。可重复读多少有点误称, 因为同样的查询执行多次并不能完全保证得到同样的结果。在隔离性为可重复读的情况下, 如果第一次检索到一个元组, 那么我们就能够确定如果重复查询的话, 将再一次检索到该元组。然而,同样查询的第二次或者以 后的执行也可能 会检索到幻象( phantom) 元组。后者是当 我们的事务在执行时, 插入到数据库中的元组。例 7. 18 让我们继续讨论在例 7. 16 和 7. 17 中的seat-choosing问题。如果我们在可重复读的隔离 性级别下执行该 函数, 那么 在第一次 查询的步 骤 1 中 的空座位 在以后的 查询中将依然为空。然而, 假定一些新的元组进入了关系Flights。例如, 航线可能把班机换成比较大的飞机, 从而产生了一些以前没有的元组, 或者有的预订被取消了。于是, 在隔离性为可重复读的情况下, 随后对空座位的查询可能会检索到这些新座位。 □7. 2. 7 本节练 习练习 7. 2. 1: 本 练习和下一 个练习 包括对不 断滚动的 PC 练 习中的 如下两个 关系进行 操作的某些程序:Product(maker,model,type)PC(model,speed,ram,hd,cd,price)用 嵌 入 式 SQL 和 合 适 的 宿 主 语 言 来 描 述 下 列 程 序。 不 要 忘 了 在 合 适 的 时 候 给 出·082·①因 为我们 还没 有描述 实施不 同隔 离性级 别的 算法, 所以 实际 发生的 事情 可能是 难以理 解的 。两个 事务可 能同时看 到 一个 座 位空 着 并且 都 想预 订 它, 其 中一 个 事务 将 会被 系 统强 制 异常 终 止, 即 使它 不 希望 执 行RO LLBA CK语句。
COMMIT 和 ROLLBACK 语句, 并告诉系统你的事务是只读的( 如果它们是的话) 。(a) B给 出 速 度 (speed) 和RAM的 容 量 ( 作 为 函 数 的 参 数 ) , 查 看 具 有 该 速 度 和RAM容量的PC, 并打印每台PC的型号(model) 和价格(price) 。* (b) 给出一个型号, 从PC和Product两个关系中删除该型号的元组。(c) 给出一个型号, 将该型号的PC的价格减少$100。(d) G给出厂商(maker) 、型号、处理器速度、RAM容量、硬盘容量、光驱(CD) 速度和价格, 检查有没有该型号的产品。如果有这种型号, 就给用户打印出错信息。如果没有这种型号, 就把该型号的信息输入到表PC和Product中。! 练习 7. 2. 2: 对于 7. 2. 1 中每个程序, 讨论是否存在原子性问题。如果有的话, 在程序执行中, 一旦系统崩溃就会发生问题。! 练习 7. 2. 3: 假定我们把练习 7. 2. 1 中的 4 个程序之一作为事务T来执行, 而执行 4 个程序中相同或者不同程序的其他事务也可能几乎同时执行。如果所有的事务不可能都运行 在SERIALIZABLE隔 离 性 级 别 下, 那 么 如 果 所 有 的 事 务 都 运 行 在READU NCOMMIT T ED隔离性级别下, 可能看到事务T的什么情况呢? 分别考虑T是练习 7.2. 1 中从(a) 到(d) 的任一个程序的情况。* ! ! 练习 7. 2. 4: 假 定我们有 一个事 务T( 一 个“永远”运行的 函数) , 每 小时都要 检验 是否有速度等于或高于 200 而售价低于$1 000 的PC。如果找到了, 就打印出该信息并且终止该事务。在此期间, 执行练习 7. 2. 1 中的 4 个程序之一的其他事务都可以运行。对于 4个隔离性级别中的每 个—— 可串行化、可重复读、读-提交和读-不提交 —— 说出在该 隔离性级别上运行的事务T会有什么影响。7. 3 SQL环 境在本节中, 我们将尽可能 广泛地描述数 据库管 理系统和 数据库 以及数据 库管理系 统所支持的程序。我们将看到数据库是如何定义以及组织群集( cluster) 、目录( catalog) 和模式( schema) 。我们也将看到程序是如何与它们要操作的数据联系起来的。许多细节取决于特定的实现, 所以我们将把重点放在 SQL2 标准所包含的一般概念上。7. 3. 1 环境SQL环境是数据在其中可以存在和对数据的SQL操作可以执行的框架。实际上, 我们应该 把SQL环 境看作是 运行在 某个设备 上的 数据库 管理 系统。例 如,ABC公 司买 了Dandy-DB公 司的SQL数 据库管 理系 统的 许可证, 从 而使 之能够 运行 在ABC公 司的 机器系列上。运行在这些机器上的系统就构成了SQL环境。我们讨 论过的所 有数据 库元素 —— 表、视图、域和 断言 —— 都 是在SQL环 境中定 义的。这些元素组成了层次结构, 在该体系结构中, 每个元素都起着不同的作用。SQL2 标准定义的结构如图 7. 11 所示。·182·
图 7. 11 SQL 环境中数据库元素的体系结构简单来说, 该组织由以下结构组成:1. 模式( schema) 。①它们是表、视图、断言、域和本书中没有讨论的其他类型信息( 见7. 3. 2 节的方框“模式 中还有什么”) 的 聚集。模式是体系 结构的基本单位, 与我们所 想象的数据库类似, 但是事实上比下面第 3 点( 群集) 中我们将看到的数据库要小。2. 目录(catalog) 。它们是模式的聚集, 是支持唯一的、可访问的术语的基本单位。每个目录都有一个或者多个模式; 在一个目录中模式的名字必须是唯一的, 并且每个目录都包含着 一个叫做 INFORMATION- SCHEMA 的 特殊的模 式, 该模式包 含着该目 录中 的所有模式的信息。3. 群集( cluster) 。它们是目录的聚集。每个用户都有相关的群集, 即该用户可以访问的所有目录的集合( 要了解如何控 制对目录和其他 元素的访问可参 看 7. 4 节) 。SQL2 没有明确描述什么是群集, 例如, 不同用户的群集如果不相同是否可以互相重叠。群集是可以查询的最大范围, 因此, 在某种意义上, 对于特定的用户来说, 群集就是“数据库”。7. 3. 2 模式模式说明的最简单格式由以下几部分组成:1. 关键字CREAT E SCHEMA。2. 模式的名字。3. 基本表、视图、断言和域之类的模式元素的说明的列表。也就是, 一个模式可以如下说明:CREATE SCHEMA〈模式名〉〈元素说明〉·282·①注 意,“模 式”这个 术语在 这段 上下文 中是指 数据 库模式 , 而不是 关系模 式。
模式中还有什么除了已经提到的表、视图、域和断言, 还有 4 种其他模式元素。首先, 一个模式可以指定字符集, 它是符号的集合以及对其进行编码的方法。ASCII 是最著名的字符集, 但是SQL2 的实现可以支持许多其他字符集, 例如各种外国语言的字符集。第二, 模式可以为 一个字符集指定 一个核对项( collation) 。回 忆一下 5. 13 节, 字符串按字 典顺序进行比较, 假定任何 两个字符都能用 我们表示为 < 的“小于”来进行比较。核对项规定哪些字符“小于”其他字符。例如, 我们可以使用 ASCII 码隐含的顺序, 或者我们可以对小写和大写字母同样处理, 而且不比较任何不是字母的东西。第三, 模 式可以有翻译, 它是把 一个字符 集的字 符转换为 其他字 符集的字 符的方法。可能出现在模式中的最后一种元素是涉及到谁访问该模式的“授权语句”。在 7. 4节我们将讨论权限的授予。元素说明的格式如 5. 7 节、5. 8 节和第 6 章所述。有些元 素我们还没有描 述其特性, 但是SQL2 允许在模式中对其加以说明; 见方框“模式中还有什么”。例 7. 19 我们可以说明一个模式, 它包括在 不断滚动的实例中 已经用到的关于 电影的 5 个关系, 加上我们已经介绍的某些其他元素, 例如视图。图 7. 12 简要地描述了这样一个说明的格式。 □ CREATE SCHEMA MovieSchemaCREATE DOMAIN CertDomain… 如例 6. 8其他域的说明CREATE TABLE MovieStar… 如图 6. 4其他 4 个表的建表语句CREATE VIEW MovieProd… 如例 5. 40其他视图说明CREATE ASSERTION R ichPres… 如例 6. 10图 7. 12 说明一个模式不需要把模 式说明全放在一 起。读者可以用合 适的 CREAT E, DROP 或者 ALT ER语句来增加或者更改模式, 例如,CREAT E T ABLE跟着该模式的一个新表的说明。问题是SQL系统需要知道新表属于哪个模式。如果我们更改或者撤消一个表或者其他模式元素, 那 么, 我们可 能还需要使元素 的名字无二 义性, 因为两个 或者更 多的模式 可能有同 样名字的不同元素。我们用SET SCHEMA语句来改变“当前的”模式。例如,SET SCHEMA MovieSchema;使图 7. 12 中描述的模式成为当前的模式, 以便模式元素的任何说明都加到该模式中或者更改已经属于该模式的元素。7. 3. 3 目录在目 录中建立和更 改模式 就像在模 式中建立 表之类 的模式元 素一样。原则上, 我们·382·
模式元素的全称形式上, 模式元素( 例如表) 的名字 是由它 的目录名、它的 模式名 和它自己 的名字用 圆点 依次 连 成的。 于 是, 其目 录 名 为 MovieCatalog、模 式 名 为 MovieSchema 的表Movie可以如下引用:MovieCatalog. MovieSchema. Movie如果目录是默认的或者是当前目录, 那么我们就可以省略这一部分。如果模式也是默认的或者 是当前模式, 那么这一部 分也可 以省略, 从而就 像通常 那样只剩 下元素自己的名字。然而, 如果需要访问在当前模式或者目录之外的某些东西, 可以选择使用全称。希望建立和移植目录的过程类似于建立和移植模式的过程。遗憾的是, SQL2 没有定义一个标准的方式来这样做, 例如语句CREAT E CAT ALOG〈目录名〉后面跟着属于该目录的模式和这些模式的说明的列表。然而,SQL2 确实规定了语句SET CATALOG〈目录名〉该语句允许我 们设置当前的目 录, 于是新 的模式将 进入该目 录并且 模式更新 将引用该 目录中的模式, 而该目录的名字可能会有二义性。7. 3. 4 SQL环境中的客户程序和 服务程序SQL环境不只是 目录和模式的聚 集。它还包括这样 一些元素, 它们的目的是支 持数据库的操作或者由这些目录和模式所体现的数据库的操作。在 SQL 环境中有两种特殊类型的处理程 序:SQL客户程序和SQL服务程序。服务程序 支持对数据库元 素的操作, 而客户程序则允许用户连接到服务程序上。可以想象, 服务程序运行在存有数据库的大型主机上, 而客户程序则运行 在另一台主机上, 或 许运行在 离服务程 序很远 的个人工 作站上。然而, 也有可能客户程序和服务程序运行在同一台主机上。7. 3. 5 连接如果我们 希望在具 有SQL客 户程序的 主机上 运行包含SQL的某 个程序, 那么可 以通过执行 SQL 语句CONNECT T O〈服务程序名〉AS〈连接名〉在客户程序和服务程序之间打开连接。服务程序名取决于设备。DEF AULT这个词可以代替名字, 它将把用户连接到其设备是“默认服务器”的任何 SQL 服务程序上。连接名可以在以后用来对连接进行引用。我们不得不引用连接的原因是SQL2 允许用户打开几个连接, 但是在任何时间只能有一个是活动的。为了在连接之间切换, 我们用语句SET CONNECT ION conn1;·482·
译 者 前 言数据 库技术近 年来发 展非常 迅速, 特别 是提 出信息 高速 公路 以来, 所谓“3C”即计 算机、通信和信息内容(Computer、Communication和Content s) 已成为信息技术的核心。而信息高速 公路的价值正体 现在信息内容上, 若没有 大量的数 据库存 放这些“内容”并提 供迅速、简便、高效的查询手段, 则信息高速公路就只能“跑空车”了。面向对象的数 据库技术是近年 来数据库 技术发展 的重要方 向和热 点, 目 前国内在 该领域的科研方 面已在积极开展, 但在教材 中尚无 反映, 因此急需 有关的 教材, 而本书正 是雪中送炭。本 书 是 从 斯 坦 福 大 学 1997 年的教材《数 据 库 系 统 基 础 教 程》(《A First Course inDatabase Systems》) 翻译过来的。本书以当前的主流数据库 —— 关系数据库 —— 为基础,以数据库系统 的最基本内容 —— 数据库 的设计与 编程 —— 为重 点, 以引进数 据库领域 的最新成果—— 比如面向对象的数据库技术—— 为特点, 系统地阐述了数据库建模、关系数据库的理论和设计、结构化查询语言SQL及其最新的标准SQL2 和SQL3, 阐述了递归查询等最 新内容, 特 别是以 相当 多的 篇幅阐 述了 面向对 象数 据库 的对象 定义 语言 ODL 和对象查询语言OQL。本书的主要特点是新颖、丰富、系统、实用, 把数据库技术的最新成果迅速反映到教材中。斯坦福大学是世界一流大学, 世界著名的硅谷与斯坦福有不解之缘。我们及时引进国外的最新教材, 对提高国内的计算机教学和科研水平会起到积极的推动作用。为培养同学直接从英文资料获取信息的能力, 清华大学出版社已于 98 年出版了本书英文原著的影印本, 现在又出版该书的中译本, 这样可使不同程度的读者都能从中有所收益。读者不仅可以从中学到最新的专业知识, 也能从中提高英文的专业阅读能力。本书的翻译得到了我系周立柱教授的大力支持, 在此表示衷心的感谢! 在本书的翻译过程中, 王霞、张勇、张劲飞和武志光同学为初稿的翻译和文稿的录入协助做了很多工作。本书的译文难免有不妥之处, 敬请读者予以指正。·Ⅰ·
人们通过利用以这种方式发现的模式, 而提高了销售额。1. 4 本 书 概 要与数据库系统有关的概念可以分为三个主要的范畴:1. 数据库设计。如何建立一个有用的数据库? 数据库应包含哪些类型信息? 信息是如何构成的? 对数据项的类型和取值有些什么假设? 数据项之间是如何联系的?2. 数据库 编程。如何表达对数 据库的查询和其 他操作? 如何使 用DBMS的其他 能力, 比如事务和触发程序?3. 数据库系统实现。如何建立一个DBMS, 包括诸如查询处理、事务处理以及为了有效的访问而进行的存储管理?虽然数据库系 统的实现是软件 行业的一 个主要部 分, 但 是设计 或使用数 据库的人 数却远远超过了建立数据库的人数。本书预计作为数据库系统的基础教程, 因此关注两个基础的部分: 设计和编程, 是恰当的。这一章我们试图使读者对第三部分—— 实现—— 有个粗浅的了解, 但以后, 我们不在本书讨论这个问题了。相反地, 本书的其余各章将按设计和编程的内容划分如下。1. 4. 1 设计第 2 章和第 3 章的内容是设计。第 2 章先介绍表达数据库设计的两种高级表示法。一种 是对 象 定义 语言 (Object Definition Language,ODL) , 即 一 种面 向 对 象的 说 明类 的 语言; 另一种 是实体/ 联系( Entity/ Relat ionship, E/ R) 模型, 即一 种用来 描述 数据库 组织 的图形表示法。无论是 ODL 还是 E/ R 模型都不 能直接 用于定义 数据库的 结构, 尽管对 于面向对 象的DBMS而言,ODL非常接近于数 据定义语言。相反地, 我们更 倾向于将这 两种模型 之一表达的设计 转换成任何一种 由数据定义语 言所使用 的正式表 示法, 而该数 据定义语 言与所使用的DBMS相关。鉴于大多数的DBMS都是关系的, 我们将集中讲解如何把ODL或 E/ R 模型转换成关系模型。所以, 第 3 章专门讲述关系模型和转换过程。然后, 5. 7 节将介绍如何用SQL语言的数据定义部分正式地描述关系数据库模式。第 3 章也 将为读者介绍“依赖”的概念, 它是正 式提出的 关系中 各元组之 间联系的 假设。依赖允许我们通过关系的所谓“规范化”的处理, 以有益的方式将关系分解。依赖和规范化将在 3. 5 节和随后的几节介绍; 它们是设计关系数据库的重要部分。不管是直接用关系模型设计数 据库, 还 是把所设 计的ODL或E/R模型转换 成关系 后发现了 设计中的 某些问题, 这部分内容都是很有用的。1. 4. 2 编程第 4 到 8 章涉及数据库编程。第 4 章从关系模型中查询的抽象处理开始, 进而介绍构成“关系代数”的一组关系操作。我们也会讨论另一种描述查询的方式, 该方式以逻辑表达式为基础, 称为“Datalog”。·51·
使 conn1 变成活动的连接。曾是当前活动的任何连接将变成待用状态, 直到用另一个明确提到它的SET CONNECT ION语句重新激活它。当我们取消该连接时也要使用该名字。我们可以用DISCONNECT conn1;取消连接 conn1。现在, conn1 结束了; 它不是待用而且不能重新激活。然 而, 如 果 我 们 永 远 不 需 要 引 用 正 在 建 立 的 连 接, 那 么AS和 连 接 名 就 可 以 从CONNECT T O 语句中省略掉。也允许完全跳过连接语句。如果我们在具有 SQL 客户程序的主机上单纯地执行SQL语句, 那么默认的连接就会按我们的需要建立起来。图 7. 13 SQL 客户程序-服务程序的相互作用7. 3. 6 会话当连 接活 动 时, 执行 的 SQL 操 作 形成 会话 (session) 。会话与 建立 它的 连 接同时 扩展。例 如, 当连 接待 用时, 它 的 会话 也 转 为 待 用, 而 且SETCONNECT ION 语 句 所引 发的 连接 的 重新激活也使会话激活。所以在图 7. 13 中我们 把连接 和会 话表示 成在 客户 程序 和服务程序之间进行联络的两个方面。每个 会话都 有当 前目 录和 该目 录 中的当前模式。像在 7. 3. 2 节 和 7. 3. 3 节讨论 的那样, 它 们可以用SET SCHEMA和SETCAT ALOG 语句来设置。我们在 7. 4 节将会讨论到, 对于每个会话都有一个授权的用户。7. 3. 7 模块模块 是用 于应 用程序 的SQL2 术 语。SQL2 标准 建议 三种类 型的 模块, 但是 只坚 持SQL 实现至少为用户提供其中一种类型。1. 通用SQL接口。用户可以坐下来并且敲入由SQL服务程序执行的SQL语句。在这种方式下, 每个查询或其他语句都是独立的模块。我们为本书中大多数例子设想的就是这种方式, 尽管实际上很少使用这种方式。2. 嵌入式 SQL。这种类型在 7. 1 节已经讨论 了, SQL 语句出现在宿 主语言程序中,并且由EXEC SQL引导。预处理程序把嵌入式SQL语句转换为适于SQL系统的函数或过程调用, 当编译好的宿主语言程序执行时, 在合适的时间执行这些调用。3. 实际 模块(true modules) 。SQL2 所设想的模 块的最常用的 形式是存 储函数或 过程的聚集, 其中一些是宿主语言代码, 而另一些是 SQL 语句。它们之间通过传递参数也可能通过共享变量来进行通信。模块的执行称为 SQL 代理( agent ) 。在图 7. 13 中, 我们给出了一个模块和一个 SQL代理, 并把两者作为一个整体调用SQL客户程序来建立连接。然而, 我们应该记住, 模块和 SQL 代理之 间的差别类似于 程序和处 理之间 的差别; 第一个 是代码, 而第 二个是执 行那些代码。·582·
7. 4 SQL2 的安全和用户授权SQL要 求 存在 授 权ID, 授 权ID基 本上 是 用户 名。SQL还 有 一 个特 定 的 授 权ID,PUBLIC, 它包括任何用户。可以授予授权 ID 权限, 很像在文件系统环境中由操作系统维护权限那样。例如,UNIX系统通常控制三种类型的权限: 读、写和执行。因为UNIX系统的保护对象是文件, 而且这三种操作正好描述了文件的典型操作, 所以该权限表是有意义的。然而数据库比文件系统复杂得多, 从而SQL2 中使用的权限类型相应地更复杂。在这一节中, 首先我们将了解对于数据库元素 SQL2 允许什么权限。然后我们将看到用户( 也就是授权ID) 如何获得权限。最后, 我们将看到如何取消权限。7. 4. 1 权限SQL2 定义了 6 种类型的权限:1.SELECT2. INSERT3.DELET E4. U PDAT E5.REFERENCES6. U SAGE其中前 4 种用于关系, 这里的关系或者是基本表或者是视图。就像它们的名字所隐含的那样, 它们分别给权限 的拥有者以下权力: 查询( select from) 关 系, 插入 到关系中, 从关系中删除, 以及修改关系的元组。如果没有与SQL语句相应的权限, 那么就不能执行包含该 SQL 语 句 的 模 块。 例 如, 一 个 select-from-where 语 句 对 它 所 访 问 的 每 个 表 都 需 要SELECT权限。我们很快就会看到模块如何得到这些权限。REFERENCES 权 限是引用在完整 性约束下的关系 的权力。这些 约束可能 采用第 6章提到的任何一种形式, 例如断言、基于属性或元组的检验, 或者参照完整性约束。如果约束所在的模式不是对 与该 约 束有 关的 所有 数据 都 有引 用( REF ERENCES) 权 限, 那么 就不能对约束进行检验。对于域, 或者除了关 系和断言( 见 7. 3. 2 节) 以外的 其他几 种模式元 素来说, USAGE权限就是在自己的说明中使用该元素的权力。人们可以给 三种权 限—— INSERT , UPDAT E 和 REFERENCES—— 加 上单独的 属性作为参数。在这种情况下, 权限只能引用提到的属性。人们可以控制每个都涉及一个属性的几种权限; 通过这种方式, 我们可以授权访问一个关系的列的任何子集。例 7. 20 让我 们考虑执 行图 5. 12 的 插入语句 ( 我 们在这里 重新生 成, 如图 7. 14 所示) 需 要 什 么 权 限。 首 先, 因 为 要 插 入 到 关 系 Studio 中, 所 以 我 们 需 要 对 Studio 的INSERT权限。然而, 因为插入只是针对属 性name的相应分 量, 所以具 有关系Studio的INSERT 权限或者 INSERT ( name) 权限都是可以的。后一种权限允许我们插入 Studio 元组中的name分量, 同时让其他分量取它们的默认值或者NULL, 也就是图 7. 14 所做的。·682·
V1) IN vSERT INTO Studio( name)2)SELECT DISTINCT studioName3) FROM Movie4) WHE QRE studioName NOT IN5) ( SELECT name6) FROM Studio) ;图 7. 14 增加新的制片公司然而注意: 图 7. 14 中的插入语句包括从 2) 行和 5) 行开始的两个子查询。为了执行这些选择, 我们要求子查询所需的权限。这样, 我们就需要对 FROM 子句中包含的两个关系Movie和St udio的SELECT权 限。 注 意, 这 只 是 因 为 我 们 拥 有 对Studio的 插 入(INSERT) 权限并不意味着我们拥有对St udio的选择(SELECT) 权限, 反之亦然。 □7. 4. 2 建立权 限我们已经明白 SQL2 权限是什么而且注意到它们对于执行 SQL 操作是需要的。现在我们必须学 习如何获得执行 某个操作所需 要的权限。 授权有两 个方面: 权限 最初是如 何建立的, 以及它们 是如何 从一个用 户传递 到另一用 户的。在这 里我们 将讨 论初始 化, 在7. 4. 4 节我们将讨论权限的传递。首先, 像模式或模块这样的 SQL 元素都有一个拥有者。某个事物的拥有者拥有 与该事物有关的所有权限。在 SQL2 中有三个时刻可以建立所有权。1. 当建立模式时, 就假定模式以及其中所有 的表和其他模式元 素都为建立它的 用户所拥有。于是该用户对该模式的元素就具有所有可能的权限。2. 当 CONNECT 语句把会话初始化时, 有机会用 USER 子句来指定用户。例如, 连接语句CONNECT TO Starfleet-sql-server AS conn1USER kirk;将为用户 kirk 建立 一个到 SQL 服务 程序的连接, 连接名为 conn1, SQL 服务程序的名 字叫Starfleet-sql-server。通常,SQL的实现将验证用户名是否合法, 例如查询一个口令。3. 当 建立模 块时, 可 以用AUTHORIZAT ION子 句作 为选 项给 模块 指 定一 个拥 有者。我们将不深入到模块建立的细节中, 因为 SQL2 标准在这方面允许其实现有相当大的灵活性。然而, 我们可以想象模块建立语句中的子句AUT HORIZAT ION picard;将使用户 picard 成为该模块的拥有者。当模块可公开执行时, 可以不指定模块的拥有者,但是在该模 块中执行任何操 作所必须的权限 必须从其他 某个来 源( 例如在该 模块执行 期间与连接和会话有关的用户) 得到。7. 4. 3 权限检 验处理像我们在上面所看到的, 每个模块、模式和会话都有一个相关的用户; 用SQL术 语来说, 其中每个都有一个相关的授权ID。任何SQL操作都有两个部分:·782·
1. 在其上执行操作的数据库元素;2. 使操作得以执行的代理。代理可行使的权限从称为当前授权ID的特定授权ID得到。如果代理执行的模块具有授权 ID, 那么特定授权 ID 就是( a) , 否则就是( b) :(a) 模块授权ID;( b) 会话授权 ID。只有当当前授权 ID 拥有了执行操作所需要的一切权限时, 我们才能执行 SQL 操作。例 7. 21 为了理解检验权限的机制, 让我们再来看一下例 7. 20。我们可以假定引用的表 —— Movie 和 Studio—— 是模式 MovieSchema 的一 部分, 用户 janeway 建立并拥 有该模式。此时, 用户janeway拥有对模式MovieSchema中的这些表 和任何其他元素 的一切权限。她可以用 7. 4. 4 节所描述的机制把一些权限授予其他用户, 但是我们假定还没有授予其他用户。例 7. 20 中的插入操作可以用几种方式执行。1. 插 入可以 作为 由用户 janeway 建 立并 含有 AUT HORIZAT ION janeway 子句 的模块的一部分来执行。模块授权ID( 如果有的话) 总是成为当前的授权ID。于是, 该模块及其SQL插入 语句 就 具有 与用 户janeway完 全相 同的 权限, 包 括 对表Movie和Studio的一切权限。2. 插入可以成为没有拥有者的模块的一部分。用户janeway在CONNECT T O语句中用USER janeway子句打开连接。现在,janeway同样成为当前授权ID, 于是插入语句具有所需要的一切权限。3. 用户janeway将 对表Movie和Studio的所有 权限授予 用户sisko, 或 者也许给 特殊用户PUBLIC( 它代表“所有用户”) 。插入语句处于具有子句AUT HORIZAT ION sisko的模块中。既然当前授权 ID 现在是 sisko, 并且该用户具有所需要的权限, 所以插入 也是允许的。4. 像在方案 3 中那样, 用户 janeway 给用户 sisko 所需要的权限。插入语句在没有拥有者的 模块中; 它是在 一个会 话中执行 的, 而 该会 话的 授权 ID 是 通过 USER sisko 子 句设置的。这样当前的授权ID是sisko, 而且该ID拥有所需要的权限。 □例 7. 21 阐明了几个原则。我们总结如下。· 如果拥有数据的用户, 其 ID 就是当前授权 ID, 那么所需要的权限总是可用的。上面的方案 1 和方案 2 说明了这一点。· 如果用户的ID是当前授权ID, 而且数据 的拥有者已经把所 需要的权限授予 了该用户, 或者如 果已经把 所需 要的权 限授 予了用 户 PUBLIC, 那 么这 些权限 就是 可用的。方案 3 和方案 4 说明这一点。· 执行 数据的拥 有者所 拥有的模 块或者执 行对数 据已有授 权的某 个用户所 拥有 的模块, 就使得所需要的权限是可用的。方案 1 和方案 3 说明了这一点。· 在会话( 它的授权 ID 是具有所需要权限的用户) 期间执行公 用模块是合法执 行该操作的另一种方法。方案 2 和方案 4 说明了这一点。·882·
7. 4. 4 授予权 限在例 7. 21 中, 我们看到拥有所需要的权限对用户( 也就是授权 ID) 的重要性。但是到现在为止, 我们看到对数 据库元素拥有权 限的唯一 方法就是 成为该 元素的建 立者和拥 有者。SQL2 提供了GRANT语句允许一个用户把权限授予另一个用户。第一个用户还保留所授予的权限; 于是可以把GRANT看成是“复制权限”。在授予的和复制的权限之间有一个重要的区别。每个权限都有相关的授权选项。也就是, 一个用户可能 对表Movie具有 带“授权选项 ”的SELECT权限, 尽管第 二个用户 可能具有同样的权限, 但是不带授权选项。于是, 第一个用户可以把对Movie的SELECT权限授予第三个用户, 而该授权可能具有也可能不具有授权选项。然而, 没有授权选项的第二个用户就不能把对Movie的SELECT权限授予其他任何用户。如果第三个用户后来得到了带授权选项的同样权限, 那么该用户可以把权限授予第四个用户, 仍然可能具有也可能不具有授权选项, 依此类推。授权语句由以下几部分组成:1. 关键字GRANT。2. 一 个或 多 个 权 限 的列 表, 例如,SELECT或 者INSERT( 名字 ) 。 可 选 的 关 键 字ALL PRIVILEGES 可以在这里出现, 作为 授权者对所讨论 的数据库元素( 在下面第 4 项提到的元素) 合法地授予所有权限的简写。3. 关键字ON。4. 一个数据库元素。典型的元素是关系, 也就是, 或者是基本表或者是视图。它也可能是域 或者我们没讨论 过的其他元素( 见 7. 3. 2 节的方框“模 式中还有 什么”) , 但是在 这些情况下, 元素名之前必须是关键字DOMAIN或者另一个合适的关键字。5. 关键字T O。6. 一个或多个用户( 授权 ID) 的列表。7. 可选的关键字WIT H GRANT OPT ION。也就是, 授权语句的格式是:GRANT〈权限表〉ON〈数据库元素〉T O〈用户表〉可能还跟着WIT H GRANT OPT ION。为了合法地执行授权语句, 执行该语句的用户必须拥有所要授予的权限, 而且这些权限 必须 带授权 选项。然 而, 授权 者可以 拥有 比所要 授予 的权 限更一 般的 权限( 带 授权 选项) 。例如, 授权者可以授予对表 Studio 的 INSERT ( 名字) 权限, 然而仍保留对 Studio 更一般的带授权选项的INSERT权限。例 7. 22 MovieSchema 模式包含表Movie( title, year, length, inColor, studioName, producerC# )Studio(name,address,presC# )它的拥有者用 户 janeway 把对 Studio 表的 INSERT 和 SELECT 权限 以及对 Movie表的 SELECT 权限授予用户 kirk 和 picard。而且, 授权的内容还包括这些权限的授权选项。授权语句是:·982·
GRANT SELECT , INSERT ON Studio T O kirk, picard WIT H GRANT OPT ION;GRANT SELECT ON Movie T O kirk, picard WIT H GRANT OPT ION;现在, picard 将同样的权限授予用户 sisko, 但是不带授权选项。语句如下:GRANT SELECT,INSERT ON Studio T O sisko; GRANT SELECT ON Movie T O sisko;同样,kirk授予sisko为进 行图 7. 14 的插 入所需 要的最小 权限, 也就 是对Studio表的 SELECT 和 INSERT ( 名字) 以及对 Movie 表的 SELECT 权限。语句如下:GRANT SELECT,INSERT( 名字)ON Studio T O sisko; GRANT SELECT ON Movie TO sisko;注意sisko从两个不同的用户得到了对表Movie和St udio的SELECT权限。他同样两 次 得 到 对 Studio 的 INSERT ( 名 字 ) 权 限: 直 接 从 kirk 得 到 以 及 通 过 一 般 性 权 限INSERT从picard得到。 □7. 4. 5 授权图因为一系列授权可能产生由授权和重叠的权限形成的复杂网络, 因此用授权图来表示授权是十分有用的。SQL系统维护着这张图的映象以记录权限及其来龙去脉; ( 取消权限的情况, 见 7. 4. 6 节) 。这张图的节点对应于一个用户和一个权限。如果用户 U 把权限 P 授予用户V, 而且该授权基于这样一个事实:U拥有权限Q(Q是带授权选项的P, 或者也可以是P 的某种广义形式, 同样带授权选项) , 那么我们就从节点 U/ Q 往节点 V/ P 画一个弧。例 7. 23 图 7. 15 是由 例 7. 22 的授权语句序列 产生的授权图。我们使 用在用户-权限组合后面跟一个星号( * ) 这样的约定来表示带授权选项的权 限。同样, 在 用户-权 限组合后面跟 两个星号( * * ) 表 示权限是从所讨 论的数据 库元素的 所有权 派生出来 的, 而 不是靠其他途径的授权。有两个星号的权限自动包括授权选项。 □7. 4. 6 取消权 限任何时候都可以把授予的权限取消。实际上, 带授权选项的权限可能已传递给其他用户, 要 取消这种权限就需 要把这些用户的 权限也 取消, 在这个意 义上, 权限的 取消可能 需要级联过程(cascade) 。取消语句的简单格式是:1. 关键字 REVOKE。2. 一个或者多个权限的列表。3. 关键字 ON。4. 一个数据库元素, 就像在授权语句组成部分的第 4 项所讨论的那样。5. 关键字 FROM。6. 一个或者多个用户( 授权ID) 的列表。也就是, 取消语句的格式如下:REVOKE〈权限表〉ON〈数据库元素〉FROM〈用户表〉·092·
图 7. 15 授权图然而, 该语句也可能包含如下各项:· 语句可以用CASCADE结尾。如果这样, 那么当取消指定的权限时, 我们也取消了只由要取消的权限所授予的任何权限。更确切地说, 如果用户 U 取消了用户 V 的权限P, 而权限P基于属于U的权限Q, 那么我们将删除授权图中从U/Q到V/P的弧线。现在, 还要删除不能从某个所有权节点( 双星号节点) 访问到的任何节点。· 语句可 以用REST RICT( 限制) 作 为结尾, 这 意味着, 如果由于要取消 的权限已 传递给 其他用 户, 而 按前一项 描述的 级联规则 将导致取 消这些 权限的 话, 那 么该 取消语句将不能执行。· 允 许用REVOKE GRANT OPT ION F OR代 替REVOKE, 在 这种 情 况下, 权 限本身 仍 然 保 留, 但 是 把 授 权 给 其 他 用 户 的 选 项 取 消 了。 这 种 选 择 可 以 与CASCADE 或者 RESTRICT 相结合, 在这种情况下, 将检验授权图看是否需 要取消其他已授予的权限。例 7. 24 继续例 7. 22, 假定 janeway 用下列语句REVOKE SELECT,INSERT ON Studio FROM picard CASCADE; REVOKE SELECT ON Movie FROM picard CASCADE;取消了授予 picard 的权限。我们删除 图 7. 15 中从janeway的这 些权限到picard的 相应权限 的弧线。由 于级联( CASCADE) 规则, 我们还应该看图中是否有任何权限不能到达双星号的( 基于所有权的)权限。检验图 7. 15, 我们看到从双星号的节点不再能到达picard的权限( 如果有另一条路径·192·
到 达 pircard 节 点, 那 么 权 限 可 能 还 存 在) 。 同 样, 不 再 能 到 达 sisko 对 表 Studio 的INSERT权限。! 这样, 我们不仅从授权图中删除picard的权限, 而且删除sisko的插入权限。注意, 我 们没 有 删除 sisko 对 表 Movie 和 Studio 的 SELECT 权限 以及 对 Studio 的INSERT( 名字) 权限, 因为它们通过kirk的权限都能到达janeway的基于所有权的权限。得到的授权图如图 7. 16 所示。图 7. 16 取消了picard权限后的授权图例 7. 25 我们将用抽象的例子来说明一些细微的 区别。首先, 当我们取消一个 一般权限 p 时, 并没有同时取消权限 p 的特殊情况。例如, 考虑下列步骤序列, 通过用户 U( 关系R的所有者) , 把对关系R的INSERT权限授予用户V, 并且把对同 一关系的INSERT(A) 权限授予V。步骤 通过 动 作1 2U GRANT INSERT ON R TO V2 2U GRANT INSERT(A)ON R TO V3 2U REVOKE INSERT ON R FROM V RESTRICT 当U取消V的INSERT权限时,INSERT(A) 权限仍然保留。步骤 2 和 3 之后的授权图如图 7. 17 所示。注意: 第二步之后出现两个独立的节点代表用户V的相似但有区别的权限。同样可以发现 步骤 3 中 的REST RICT选项 没有阻 止取消权 限, 因为V并没 有给 其他任 何用 户授权的选项。事实上, V 不能授予任何权限, 因为 V 得到的是不带授权选项的权限。 □·292·
图 7. 17 取消一般的权限留下更具体的权限例 7. 26 现在, 让我们 考虑一 个相似 的例 子:U授予V带 授权选 项的 权限p, 然 后只取消授权选项。在这种情况下, 我们必须改变 V 的节点来反映授权选项的丢失, 并且必须通过去掉来自V/P节点的弧线来表明取消由V授予的任何权限p。步骤序列如下:步骤 通过 动 作1 2U GRANT p TO V WITH GRANT OPTION2 2U GRANT p TO W3 2U REVOKE GRANT OPT ION FOR p FROM V CASCADE 步骤 1, U 把带授权选项的权限 p 授予 V。步骤 2, V 利用授权选项把 p 授予 W。于是授权图如图 7. 18(a) 所示。然后是步骤 3, U 取消了 V 的权限 p 的授权选项, 但是没有取消权限本身。这样, 星号就从V/p的节点去掉了。然而, 没有星号的节点就不会有弧线引出, 因为这样的节点不可能成为授权的来源。所以我们还必须去掉从 V/ p 节点到 W/ p 节点的弧线。现在, 节点W/p没有一条 从双 星号 节点( 它 代表权 限p的 起点) 来 的路 径。作为 结果, 把节点 W/ p 从图中删除。然而, 节点 V/ p 仍然保留, 只是通过去掉代表授权选项的星号做了更改。最后得到的授权图如图 7. 18(b) 所示。 □图 7. 18 取消授权选项留下基本的权限7. 4. 7 本节练 习练习 7. 4. 1: 指出执行下列查询需要什么样的权限。对于每种情况, 都要提到最具体的权限和一般的权限。( a) 图 5. 3 的查询。·392·
( b) 图 5. 5 的查询。* (c) 图 5. 12 的插入。(d) 例 5. 29 的删除。(e) 例 5. 31 的修改。( f) 图 6. 4 基于元组的检验。( g) 例 6. 10 的断言。* 练习 7. 4. 2: 画出图 7. 19 列出的动作序列的步骤 4 到 6 以后的授权图。假定A是权限p所涉及的关系的拥有者。步骤 通过 动 作1 A GRANT p TO B WITH GRANT OPTION2 A GRANT p TO C3 B GRANT p TO D WITH GRANT OPTION4 D GRANT p TO B,C,E WITH GRANT OPTION5 B REVOKE p FROM D CASCADE6 A REVOKE p FROM C CASCADE图 7. 19 练习 7. 4. 2 的动作序列练习 7. 4. 3: 画出图 7. 20 列出的动作序列的步骤 5 和 6 执行完以后的授权图。假定A是权限p所涉及的关系的拥有者。步骤 通过 动 作1 IA GRANT p TO B,E WITH GRANT OPTION2 IB GRANT p TO C WITH GRANT OPTION3 IC GRANT p TO D WITH GRANT OPT ION4 IE GRANT p TO C5 IE GRANT p TO D WITH GRANT OPT ION6 IA REVOKE GRANT OPTION FOR p FROM B CASCADE图 7. 20 练习 7. 4. 3 的动作序列! 练习 7. 4. 4: 画 出下面这 些步骤之 后的最 后授 权图, 假定A是权 限p所 涉及的 关系 的拥有者。步骤 通过 动 作1 2A GRANT p TO B WITH GRANT OPTION2 2B GRANT p TO B WITH GRANT OPTION3 2A REVOKE p FROM B CASCADE7. 5 本 章 总 结 嵌入式SQL: 不使用通用查 询接口来表达SQL查询和更新, 而是把SQL查 询嵌·492·
第 5 到 7 章的内容是 SQL 编程。正如我们曾提到的, SQL 是当前居支配地位的查询语言。第 5 章介绍了有关SQL查询的基本概念, 以及SQL数据库模式的表达式。这一章和随后两章的几乎所有内容都是基于称为 SQL2 的 SQL 标准版本的。不 过, 一些商 用系统的SQL编程的某些方面并未包括在SQL2 中。这种情形下, 我们就使用新近的标准, 叫做 SQL3, 但它还未被正式采纳。第 6 章涉及到SQL数据的约束和触发方面。由于SQL2 在这些方面 的局限性, 因此我们除了介绍 SQL2 以外, 还专门花些时间介绍 SQL3 中的约束和触发的处理。第 7 章将介绍SQL编程中的一些高级方面。首先, 虽然SQL编程的最简单的模型是独立于操作系 统的通用的查询 接口, 但实 际上, 大 多数 SQL 编程都是 嵌入到用 C 之类 的传统语言编写的更大的程序中。在第 7 章, 我们将学会如何把SQL语句与外层程序 结合起来, 并且在数据库和程序变量之间传递数据。这一章还包括如何使用 SQL 的如下特性:定义事务, 连接客户程序和服务程序, 授权他人访问数据库。第 8 章我们的注意力转移到面向对象数据库编程的正在形成的标准上。在这里, 我们考 虑 两 个 方 向: 第 一,OQL( 对 象 查 询 语 言,Object Query Language) , 可 以 看 作 是 使C+ + 与高级数据库编程命令兼容的一种尝试; 第 二, 就 是 SQL3 具 有 的 面 向 对 象 特 性,可以看作是使关系数据库和SQL与面向对象编程兼容的一种尝试。在一定程度上, 这两种方法有着共同的基础。但是, 它们也存在一些本质的区别。1. 5 本 章 总 结 数据库管理系 统: DBMS 允许设 计者构造自己的 信息, 允许 用户查询和更新 这些信息, 并帮助管理海量数据和对数据的许多并发操作。 数 据库语言: 有些 语言或 语言成 份用 于定义 数据 的结构 ( 数据定 义语 言) 以及 查询和更新数据( 数据操作语言) 。 关系 数据库系 统: 今天, 大部 分的 数据 库系统 以数 据的关 系模 型为 基础, 也就 是把信息组织成表。SQL是这些系统中最常用的语言。 面向对象的 数据库系统: 一些现代的数 据库系统使用面 向对象的数据建 模思想,包括类, 很大的类型系统, 对象标识和子类的继 承性。将来, 大多数数据库管 理系统, 包括关系数据库管理系统, 都将支持部分或全部上述概念。 第二和第三存储设备: 大的数据库存储在第二存储设备上, 通常是磁盘。极大的数据库需要第三存储设备, 它们的容量比磁盘大几个数量级, 但速度也慢几个数量级。 DBMS的组成: 数据库管 理系统的主要组 成部分是查询处 理程序、事 务管理程 序和存储管理程序。 存储管理程 序: 存储管 理程序既要管理 第二存储设备上 的数据文件, 又要管理 包含这些文件一部分内容的内存缓冲区。数据库管 理系统一般会维 护索引, 而 索引这种数据结构可支持对数据的有效访问。 查 询管理程 序: 查询管理 程序 的一 个重要 任务 是“优化”查 询, 也就 是说, 为给 定的查询寻找好的解答算法。·61·
入到传统的宿主语言中, 这样编写的程序通常更有效。 匹配失衡:SQL的数据模型与传统的宿主语言的数据模型有很 大的不同。因此,信息就 通过共享 变量在 SQL 和宿主 语言之 间进行 传递, 而共 享变 量代表 程序 中SQL部分的元组分量。 游标: 游标是指向关系的一个元组的 SQL 变量。尽管可以把当前元组的分量取到共享变量并通过宿主语言进行处 理, 但是 通过游标覆盖该关 系的每个元组使 得宿主语言和SQL之间的连接变得更加方便。 动态 SQL: 不是 在宿主 语言程 序中嵌 入特 定的 SQL 语句, 而 是宿 主程序 可以 产生字符串, 这些字符串由SQL系统作为SQL语句进行解释和执行。 并发控制: SQL2 提供两种机制以防止并发操作互相干扰: 事务和 对游标的限制。对游标的限制包括说明游标为“不敏感的”(insensitive) , 在这种情 况下, 游标 看不到关系的任何变化。 事务:SQL允许程 序员把SQL语句组 成事务, 它可 能提交, 也可 能退 回( 异常 终止) 。在后一种情况下, 将取消事务对数据库所做的任何改变。 隔离 性级别:SQL2 允许 事务在四 种隔离 性级别上 运行, 从最严 格的到最 不严 格的 分别称为: “可串行 化”( 事务必须看来 好像完全在某个其 他事务之前或者 之后运 行) ,“可重复 读”( 如 果重复查 询, 那 么响应 该查询而 读出的 每个元组 都将再 次出 现) ,“读-提交 ”( 只 有已经 提交 的事务 才能 看到 该事物 所写 的元组 ) , 和“读-不提交”( 对于事务可能看到什么没有限制) 。 只读游标和事务: 可以把游标或者事务说明为只读的。该说明是该游标或者事务将 不改变数 据库的 保证, 于是就 可以通 知SQL系统: 它不 会在 违背 不敏感 性、可串行性或者其他要求的情况下影响其他游标或者事务。 数据库的体系结构: 利用具有SQL2DBMS的设备建立SQL环境。在该环境中,像关系这样的数据库元素将组成数据库模式、目录和群集。目录是模式的聚集, 群集是用户可以看到的元素的最大聚集。 客 户程 序/ 服 务 程序 系统:SQL客 户程 序连 接 到SQL服 务程 序, 建 立 一个 连 接( 两个进程 之间的 连接) 和一个 会话( 操作的 序列) 。会话期 间执行的 代码来自 模块, 该模块的执行称为SQL代理。 权限: 出于安全的考虑, SQL2 允许对数据元素有许多不同种类的权限。这些权限包 括查询 ( 读) 、插入、删除或 修改关系 的权限 以及引用 关系( 在约 束中 引用 它们)的权限。还可以按关系的特定列获得插入、修改和引用的权限。 授权图: 权限可以由拥有者授予其他用户或者一般用户PU BLIC。如果授权时带授权选项, 那么这些权限可以再传给其他用户。也可以取消权限。授权图是一种有用的方式, 它可 以完全 记住授权 和取消 的历史, 从而确 定谁拥 有什 么样的 权限 以及他们是从哪里得到这些权限的。·592·
7. 6 本章参考文献读者要再一次参考第 5 章文献目录的注释以获得SQL2 标准的信息。[ 1] 中给出了在事务和游标领域关于该标准问题的讨论。实现事务的最重要的“二段锁”思想在[ 3] 中提及。关于事务管理和实现的更多信息,看[ 2] 和[ 5] 。关于 SQL2 授权的概念来源于[ 6] 和[ 4] 。[ 1] 'Berenson, H. , P. A. Bernstein, J. N. Gray, J. Melton, E. O' Neil, and P. O' Neil, A critiqueof ANSI SQL isolation levels,Proceedings of ACM SIGMOD Intl.Conf.on Management ofData,pp. 1~10, 1995.[ 2] 'Bernstein, P. A. , V. Hadzilacos, and N. Goodman, Concurrency Control and Recovery inDatabase Systems,Addison-Welsey,Reading,MA, 1987.[ 3] 'Eswaran, K. P. , J. N. Gr ay, R. A. Lorie, and I. L. Traiger, The notions of consistency andpredicate locks in a database system. ,Communications of the ACM, 19: 11,pp. 624 ~ 633,1976.[ 4] 'Fagin,R. ,On an author ization mechanism,ACM Transactions on Database Systems, 3: 3,pp.310~319, 1978.[ 5] 'Gray, J. N. and A. Reuter, Transaction Processing: Concepts and Techniques, Morgan-Kaufmann,San Francisco, 1993.[ 6] 'Griffiths, P. P. and B. W. Wade, An authorization mechanism for a relational database system,ACM Transactions on Database Systems, 1: 3,pp. 242~255, 1976.·692·
第 8 章 面 向 对 象 查 询 语 言 在 这 一 章, 我 们 将 讨 论把 面 向 对 象 的 编 程 引 进 数 据 库 世 界 的 两 种 尝 试。OQL 和SQL3 这两种 语言的 标准正在 形成之中, 并 没有广 泛实 现, 但是它 们正 为人 们所接 受, 同时其思想正在迅速地渗入到商业系统中。OQL, 即对象查询语言(Object Query Language) , 是这样一种尝试, 它希望把面 向对象的查询语言 标准化为一种 语言, 这种语言 能将高 级的、说明性的 SQL 编程 和面向对 象的编程范例结合起来。我们在本章开始先讨论ODL中的方法和范围(ODL即第 2 章中作为建模工具介绍的对象定义语言) 。这两个特性对查询语言 OQL 有着重要的影响。然后我们将介绍OQL编程的许多内容。如果说 OQL 试图把 SQL 的精华引进面 向对象的 世界, 那么就可 以把 SQL3 描述 为把面向对象的精华引进关系的世界。在某种意义上, 这两种语言“在中间相遇”, 但是两者在方法上也存在显著差别, 以至于某些东西用一种语言比用另一种更容易实现。所以, 在介绍了SQL3 的推荐标准的面向对象特性之后, 我们将对这两种语言的能力进行比较。实质 上, 这 两种面向 对象方 法的不同 之处在 于, 它 们对“关系有 多么重要 ”这 个问 题的回答不同。对于以ODL和OQL为中心的面向对象的群体, 答案是“不很重要”。于是,在这种方法中, 我们可以找到所有类型的对象, 其中一部分是集合或者结构( 也就是关系)的包。对于SQL3 群体, 答案是关系仍然是基本的数据建构概念。在通常称为对象关系的SQL3 方法中, 把关系模 型扩充了, 它允许关系的 元组和属于关系属 性的域有更为复 杂的类型。这样, 对象和类都引入到关系模型中, 但总是处在一个关系中。8. 1 ODL 中相关查询的特性在这一节, 我们将继续 第 2 章中关于ODL的 讨论。首先我们将讨 论ODL类与其 所处的更大编程环境之间交互方式的问题。然后我们将讨论类扩充的问题, 对于 OQL 来说它将起到类似于关系在SQL中的作用。8. 1. 1 ODL 对象的操作我们很快将会看到, OQL 是这样一种语言, 它允许我们像 SQL 那样来表示具有 关系的或者基于集合行为的操作。然而在许多情况下, 还需要执行不基于集合的其他操作。例如, 如果对象是文档, 我们可能希望检验在给定的文档中是否包含给定的关键字。如果对象是地图或者图片元素, 我们也许希望在合适的位置显示该对象。即使是传统的面向记录的数据( 比如我们不断滚动的电影实例) 也 受益 于 某些 特 殊 的操 作( 比如产生某个给定影·792·
为什么要署名?提供的署名值是当我们用真正的编程语言来实现模式时, 我们能够自动检验实现与用模式表示的设计是否匹配。我们不能检验实现能够准确完成操作的“意义”, 但是我们至少能够检验输入和输出参数的准确数目和正确类型。星每年所主演电影数量的统计图表) 。由 传统 的或者 宿主 的编程 语 言( 例如 C) 编写 的程 序 执行 这些 SQL 操 作, 在 程序 中SQL语句 是嵌入式的。值通过 7. 1 节所介绍的 机制在SQL变量和 宿主语言变量之 间进行传递。ODL定义和宿主语言之间是紧密结合的。假定宿主语言是C++ 或者Smalltalk这样面向对象的语言。每种语言都非常类似于ODL, 可以把ODL说明直接转换为宿主语言说明。更进一步说, 表示对象的宿主语言变量, 很容易表示 ODL 语句中说明的对象。①为了使ODL说明和OQL查询与宿主语言之间的结合更加方便,ODL允许第三种特性( 除了属性和联系) : 方法。方法是与类相关的函数。它应用于该类的对象并可能有一个或多个其他参数。我们将会看到方法可以用在OQL中, 似乎它们是类的属性。8. 1. 2 ODL中方法署名的说 明在ODL中, 我们可以说明 与类相关的方法的 名字和 这些方法 的输入/ 输出类 型。这些说明 称为署 名, 如 同C或 者C++ 中 的函数说 明( 与函数 定义不 同, 函数 定义给 出实 现该函数的代码) 。方法的实际代码是用宿主语言写的, 它的代码不是ODL的一部分。方法说明与接口说明中的属性和联系一起出现。每种方法都和一个类( 也就是和一个接口) 相关, 同时由该类的对象所引用, 这种形式是面向对象语言的标准形式。这样, 对象就是该方法的隐式自变量。这种形式允许同一方法名用于几个不同的类, 因为执行操作的对象决定了方法的特定意义。这样的方法名称为重载( 作为多个类的方法出现) 。方法说明的语法类似于 C 的函数说明, 这里有两点重要补充:1. 规定函数参数为输入、输出或者输入输 出, 意味着 它们分别作为输 入参数、输 出参数或者输入输出参数。函数可以修改后两种参数; 而不能修改输入参数。实质上, 输出和输入输出参数是通过引用传递的, 而输入参数则通过值来传递。注意, 函数可能有返回值,它是由函数产生的结果, 而不是由赋值给输出或者输入输出参数产生的。2. 函数可能引发异常, 这些异常是正常的参 数传递之外的特殊 响应和函数间互 相通信的响应机制。异常通常指非正常或者非希望的情况, 这种情况将通过调用它( 或许通过一系列的调用间接调用它) 的某个函数来“处理”。除以 0 就是可以作为异常情况的例子。ODL函数 说明后面可以跟 着关键字raises( 引发) , 随后的括 号里是 该函数可 能引发的 一个或者多个异常的列表。·892·①回 忆一下SQL, 宿 主语 言的变 量类 型( 像整数 ) 不 能很 好地 匹配SQL的基 本数 据类 型( 元 组和 关系 ) 。如 7. 1节中我 们所 看到的 , SQL 和 宿主 语言的 结合 相当不 便。
例 8. 1 在图 8. 1 中, 我们将看到图 2. 6 所示的类 Movie 的扩充的接口定义。这里有两个与方法无关的改动。1. 2) 行的“范围说明”。该语句的目的将在 8. 1. 3 节加以说明。2. 3) 行说明title和year是Movie的键码。接口说明中包括的方法如下所示。10) 行说明了方法lengthInHours。我们可以认为该方法将产生一个返回值, 该返回值就是应用该方法的电影对象的长度, 而长度为从用分钟表示( 和属性lengt h所表示的一样) 转换为用小时表示的等值浮点数。注意该函数没有参数。应用该方法的 Movie 对象是“隐式”自变量, 根据该对象 lengt hInHours 的可能实现将获得用分钟表示的电影长度。我们 还 将 看 到 该 函 数 可 能 引 发 名 为 noLengthFound 的 异 常。 如 果 应 用 方 法lengt hInHours的对 象的length属性 值无 定义 或者不 代表 有效长 度( 例如 是负 数) , 那 么估计就会引发这种异常。 ?1) intberface Movie2) (extent Movies3)key(title,year) ){4) attribute string title;5) attribute integer year;6) attribute integer length;7) attribute enumeration( color , blackAndWhite) filmType;8) brelationshipESet〈Star〉starsinverse Star∷starredIn;9)relationship Studio ownedByinverse Studio∷owns;10)float lengthInHours( )r aises(noLengthFound) ;11)starNames(out Set〈String〉) ;12)otherMovies(in Star,out Set〈Movie〉)raises(noSuchStar) ;};图 8. 1 向 Movie 类增加方法署名记住, 说明中并不需要方法来完成其名字所隐含的事情。例如, 不管方法应用于什么样的Movie对象, 都用总是 返回 3. 14159 的 函数来 实现方法lengthInHours, 人们都会 认为是正确的。我们也可以这样实现该方法: 返回转换为浮点数的长度的平方。只要无自变量( 除了应 用它的 对象) 、返 回一个浮 点数 而且除 了noLengthFound之 外没 有其他 异常,任何函数都可以接受。我们在 11) 行看到另一个方法署名; 该署名是名为starNames的函数。该函数没有任何返回值, 但是有一个其类型是字符串集合的输出参数。我们可以假定输出参数的值由该函数计算成字符串的集合, 该字符串集是应用该函数的电影明星的属性 name 的值。然而同以前一样, 这里并没有保证所实现的函数以这种特定的方式执行。最 后, 12) 行是 第三 个方法, otherMovies。该 函数的 输 入参 数类 型为 Star。 该函 数的可能实 现如下。我 们可以 假定otherMovies希望该 影星 成为这 部电 影中 的明星 之一,·992·
如果不是这样, 那么就会引发异常 noSuchStar。如果他是应用该方法的电影的影星之一,那么 就将 给 出该 影星 所有 其他 电 影的 集 合, 而 把它 作 为其 类 型为 电 影 集合 的 输出 参 数的值。 □8. 1. 3 类的范 围每个 ODL 类( 接口) 可以有一个说明的范围, 它是该类对象的当前集合名。说明的格式是关键字 extent 跟着为范围选择的名字。范围说明必须紧跟在接口( 类) 名说明之后。在某种意义上, 类的范围类似于关系名, 而类定义本身则类似于该关系属性的类型说明。我们将会看到OQL查询引用的是类的范围, 而不是类名本身。例 8. 2 图 8. 1 的 2) 行 给 出 了 类 Movie 的 范 围 定 义。 该 范 围 的 名 字 是 Movies。Movies的值在任何时候都是数据库中此刻存在的所有Movie对象的集合。 □8. 1. 4 本节练 习练习 8. 1. 1: 图 8. 2 是我们不断滚动的产品练习的ODL描述。我们已经使得这 3 种类型产品的每类 都成为主Product类的子类。读 者将会发现: 产品的类型既 可以从type属 性得到, 也可以从它所属的子类得到。这种安排不是一个出色的设计, 因为它允许这种可能性, 比如说,PC对象将具有与“laptop”或者“printer”相同的类型(type) 属性。然而, 这种安排对如何表达查询给出一些有趣的选项。因 为 Printer 从它 的超 类 Product 继承 了 类型 ( t ype) , 所以 我 们将 不 得不 将 Printer的类型属性改名为 print erType。后一个属性给出了打印机所使用的处理方式( 例如, 激光或者喷墨) , 而Product的类型则具有像PC、便携式电脑或者打印机之类的值。把适合于做以下事情的函数作为方法署名加到图 8. 2 的 ODL 代码中。* ( a) 从产品的价格中减去 x。假设 x 是函数的输入参数。* (b) N如 果 产 品 是“PC”或 者 “laptop”, 则 返 回 它 的 速 度。 否 则 就 引 发 异 常“notComput er”。( c) 设置便携式电脑的的屏幕尺寸为特定的输入值 x。! (d) F给出输入产品p, 确定应用该方法 的产品q是否比p的速度更 快而价格更低。如 果p是 没有 速 度 的 产 品( 也 就 是, 不 是PC机 或 便 携 式 电 脑) 就 引 发 异 常badInput, 而如果 q 是没有速度的产品, 则引发异常 noSpeed。练习 8. 1. 2: 图 8. 3 是我们不断滚动的战列舰(batt leships) 数据库的ODL描述。增 加下列方法署名:( a) 计算一艘舰艇的火力, 也就是火炮的数量乘以火炮口径的立方。( b) 找出一艘舰艇的姐妹舰艇。如果该等级的舰艇只有一艘就引发异常 noSisters。(c) 给出一次战役b作为参数, 并且把该方法 应用于舰艇s。找 出战役b中沉没 的舰艇, 假 设 s 参 加 该 战 役。 如 果 舰 艇 s 没 有 参 加 战 役 b, 就 引 发 异 常didNot Participate。(d) 给出名字和下水年份作为参数, 把该名字和年份的舰艇加到应用该方法的类中。·003·
Winter&face Product( extent Productskey model){ attribute integer model;attribute stringmanufacturer;attribute str ing type;attribute r eal price;};interface PC∶ Product(extent PCs){ attribute integer speed;attribute integer ram;attribute integer hd;attribute str ing cd;};interface Laptop∶Product( extent Laptops){ attribute integer speed;attribute integer ram;attribute integer hd;attribute r eal screen;};interface Printer∶Product(extent Pr inters){attribute boolean color;attribute str ing printertype;};图 8. 2 ODL 的产品( Product) 模式 in terface Class(extent Classeskey name){ attribute string name;attribute string country;attribute integer numGuns;attribute integer bore;attribute integer displacement;relationship Set〈Ship〉ships inverse Ship∷classOf;};interface Ship( extent Shipskey name){ attribute string name;attribute integer launched;relationship Class classOf inverse Class∷ships;relationship Setr〈OutCome〉inBattlesinverse Outcome∷theShip;};interface Battle( extent Battleskey name){ attribute string name;attribute Date dateFought;relationship Set r〈Outcome〉resultsinverse Outcome∷theBattle;};interface Outcome( extent Outcomes){ attribute enum Stat{ok,sunk,damaged}status;relationship Ship theShip inverse Ship∷inBattles;relationship Battle theBattle inverse Battle∷results;}; 图 8. 3 ODL 的战列舰数据库8. 2 OQL介 绍在这一节, 我们将介绍OQL(Object Query Language) , 即对象查询语言。我们在本节和下面两节 涉及的范围比SQL中的范 围稍有扩 大。我们将 解释那 些最重要 的语句和 特性, 但是OQL还有许多其他 能力。通常这些能力 类似于SQL或者 典型的面 向对象传 统编程语言中的相应特性。OQL 不允许我们像传统编程语言 C 那样表达任意函数。相反, OQL 提供给我们类似SQL的表示法, 这种表示法比传 统语言的典型语 句在更高级的抽象 层次上表达特定 的查询。 人 们的 意 图是 把OQL作 为 某个 面 向对 象 的 宿主 语 言( 例 如C+ + 、Smalltalk或 者·103·
Java) 的扩充。这些对象将由 OQL 查询和宿主语言的传统语句进行操作。将宿主语言语句和OQL查 询混合 起来 而不是 在两 种语 言之间 显式 传递值 的能 力比 起将SQL嵌入 到宿主语言中( 如 7. 1 节所讨论的) 这种方式是一个进步。8. 2. 1 面向对 象的电影 实例为了说明 OQL 的句法, 我们需要不断滚动的电影实例。它包括熟悉的类 Movie、Star和 Studio。我们将使用图 8. 1 中 Movie 的定义。另一方面, 我们从 图 2. 6 中得到 Star 和Studio的定义, 用键码和范围说明来扩充它们, 但是没有用方法; 见图 8. 4。 inter face Star( extent Starskey name){attribute string name;attribute Struct Addr{string street, string city} address;relationship Set0<Movie>starr edIninverse Movie∷starts;};interface Studio( extent Studioskey name){attribute string name;attribute string address;relationship Set 0< Movie> ownsinverse Movie∷ownedBy;};图 8. 4 面向对象的电影数据库的一部分8. 2. 2 OQL类型系统OQL 中的类型与 ODL 说明中的一样多( 见 2. 1. 7 节) 。然而 OQL 对于类型构造符的嵌套深度没有限制。当我们讨 论编程语言的类 型系统时, 需要区别 说明变量 的类型 ( 有时 称为可 变对象)和表达常量的值( 有时称为不可变对象) 。OQL语句所用的变量将在外层的宿主语言中说明, 可能使用在 2. 1. 7 节介绍的 ODL 表示法或者类似的某种方法。ODL 作为数据定义语言不需要常量, 但是OQL程序需要。这样, 我们就需要了解在OQL中如何构造任意类型的常量。常量是由下列的基本类型和类型构造符构成的。1. 基本类型, 包括两部分:(a) g原子类型: 整数、浮点数、字符、字符串和布尔类型。它们像SQL那样表示, 除了字符串要用双引号括起来。·203·
用箭头代替点OQL把箭头- > 作为点的同义词。这种约定有点C的风格,C的点和箭头都能获得结构的分量。然而, C 的箭头和点运算符的含义稍微有些不同; 但是在 OQL 中它们是相同的。在C中, 表达式a.f要求a是一个结构, 而p- >f要求p是结构的指针。两者都产生该结构的域 f 的值。( b) k枚举类型: 枚举类型中的值实际上是在 ODL 中说明的。任何这样的值都可以作为常量。2. 由下列类型构造符构成复杂类型。( a) Set( …) 。(b)Bag( …) 。( c) List (…) 。( d) Array( …) 。(e)St ruct( …) 。前四种称为聚集类型。聚集类型和结构( st ruct) 可以用来作为任何合适类型( 基本的或者复杂的) 的值。然而, 当应用Struct运算符时需要指定域名及其相应的值。每个域名都跟着一个冒号和它的值, 域-值对之间用逗号分开。例 8. 3 表达式 bag( 2, 1, 2) 表示在包( bag) 中整数 2 出现 2 次, 整数 1 出现 1 次。表达式st ruct( foo∶ bag( 2, 1, 2) , bar∶ ″baz″)表示具有两个域的结构。一 个名为foo, 用上面描述的包作 为它的值; 另一个名为bar, 其值为字符串″baz″。 □8. 2. 3 路径表 达式我们通过采用 点表示法的复杂 类型来访问变量 的分量, 点 表示法类似于 C 中所用 的点, 也与 SQL 中所用的点有关。一般规则如下。如果 a 表示属于类 C 的对象, p 是该类的某个 特性 —— 可 以是 该类 的属 性、联系 或 者方 法—— 那 么a.p就 表示 把p用于a的 结果。也就是:1. 如果p是属性, 那么a.p就是对象a的该属性值。2. 如果p是联系, 那么a.p就是通过联系p与a相连的对象或者对象的聚集。3. 如果 p 是方法( 或许带参数) , 那么 a. p 就是把 p 用于 a 的结果。例 8. 4 假定myMovie是宿主语言变量, 它的值是Movie对象。那么· myMovie. length 的值 是该 电影的 长度, 也 就是, 由 myMovie 所表 示 的 Movie 对象中length属性的值。· myMovie.lengthInHours( ) 的值是实数 ( 用 小时 表示的 电影 长度) , 是通过 把方 法lengt hInHours 用于对象 myMovie 计算出来的。· myMovie.stars的值是通过联系st ars与电影myMovie相连的St ar对象的集合。·303·
· 表达 式 myMovie. starNames( myStars) 不 返回任何值( 也就 是, 在 C+ + 中该表 达式 的 类 型是void) 。然 而, 作为 附 带的 效果, 它 设置 方 法st arNames的 输出 变 量myStars 的值为字符串的集合; 这些字符串是电影的影星名。 □为了有意义, 我们可以用几个点形成表达式。例如, 如果myMovie表 示电影对象, 那么 myMovie. ownedBy 就表示拥有该电影的 Studio 对象, 而 myMovie. ownedBy. name 则表示作为该制片公司名字的字符串。8. 2. 4 OQL 中的 select-fr om-where 表达式OQL 允 许我 们用与 众 所周 知的 SQL 查 询 格式 类似 的 select-from-where 句法来 写表达式。这里是查询电影《乱世佳人》(Gone With the Wind) 年份的例子。 SELECT m. year FROM Movies m WHERE m. titlte = ″Gone With the Wind″注意, 除了字符串常量前后的双引号以外, 该查询更像SQL而不是OQL。唯一 不明显的差别就是 SQL 中希望将 F ROM 子句写成 FROM Movies AS m然而, OQL 中关键字 AS 是可选的, 就像 SQL 一样。OQL 中省略它显得更有意义, 因为短语“Movies m”的含 义是:m是依 次引 用范 围Movies中每 个对象 的变 量; 后者的 范围 是Movie 类的当前对象集合。一般说来,OQL的select-from-where表达式的组成如下:1. 关键字 SELECT 后面跟着表达式的列表。2. 关键字FROM后面跟着一个或多个变 量说明的列表。变量 通过给出以下三 项来说明: (a) 其值为聚集类型( 例如集合或包) 的表达式,( b) 可选关键字 AS, 和(c) 变量名。典 型的 情况是, ( a) 的表 达式 是某个 类的 范围, 例如 上面 例子 中的范 围 Movies 对 应于 类Movie。范围类似于SQL FROM子句中关系。然而, 可以将产生聚集的任何表达式( 例如另一个 select-from-where 表达式) 用在变量说明中。对于这种能力 SQL2 不能直接模拟,不过某些商业的SQL允许FROM子句包含子查询。3. 关键字 WHERE 和布尔值表达式。该表达式类似于 SELECT 之后的表达式, 只是用常量和FROM子句中说明的那些变量作为运算数。比较运算符类似于SQL, 除了用“! = ”而不是“< > ”来表示“不等于”。逻辑运算符类似于 SQL, 也是 AND, OR 和 NOT 。查询将产生对象的包。在嵌套的循环中, 我们通过考查FROM子句中变量的所有可能的值来计算这个包。如果这 些变量值的任何 组合都满足 WHERE 子句 的条件, 那 么就把SELECT子句所描述的对象加到包中, 该包就是select-from-where语句的结果。例 8. 5 下面是说明 select-from-where 结构的更加复杂的 OQL 查询。 SELECT s.name·403·
事务管理程序: 事务是数据库的基本工作单元。事务管理程序在确信事务具有 ACID特性, 即原子性、一致性、隔离性和持久性的情况下, 允许许多事务并发地执行。 客 户程序-服 务程 序系 统: 数据 库 管理 系统 通常 支持 客 户程 序-服务 程序 体系 结构, 数据库的主要部分都在服务程序中, 而客户程序则负责用户接口。 数据库的主 动性元素: 现代数据库系统 支持某种形式的 主动性元素, 通常是触 发程序和/ 或完整性约束。 未来的系 统: 数据库 系统的主要发展 方向包括支持像 视频或图象那样 庞大的“多媒体”对象, 以及将许多独立信息源的信息集成为单一的数据库。1. 6 本章参考文献许多书的内容涉及到数据库系统实现的重要方面。[ 3] 和[ 5] 涉及到事务管理程序的实现。这些著作和[ 7] 都论述了分布式数据库的实现。[ 11] 讨论了文件管理程序的实现。[ 2] 、[ 4] 和[ 6] 论述了 面向对象的数据库 系统。[ 1] 、[ 9] 和[ 10] 涉及数据 库系统的 理论。在[ 8] 中可以找到许多对数据库领域的研究做出贡献的论文。[ 1] 'Abiteboul, S. , R. Hull, and V. Vianu, Foundations of Databases, Addison-Wesley, Reading,MA, 1995.[ 2] 'Bancilhon,F. ,C.Delobel,and P.Kanellakis,Building an Object-Oriented Database System,Morgan-Kaufmann, San Francisco, 1992.[ 3] 'Bernstein,P.A. ,V.Hadzilacos,and N.Goodman,Concurrency Control and Recovery inDatabase Systems, Addison-Wesley, Reading, MA, 1987.[ 4] 'Cattell,R.G.G. ,Object Data Management,Addison-Wesley,Reading,MA, 1994.[ 5] 'Gray, J. N. and A. Reuter , Transaction Processing: Concepts and Techniques, Morgan-Kaufmann,San Francisco, 1993.[ 6] 'Kim,W. (ed. ) ,Modern Database Systems:The Object Model,Interoperability,and Beyond,ACM Pr ess, New York, 1994.[ 7] 'Oszu,M.T.and P.Valduriez,Principles of Distributed Database Systems,Prentice Hall,Englewood Cliffs, NJ, 1991.[ 8] 'Stonebraker,M. (ed. ) ,Reading in Database Systems,Morgan-Kaufmann,San Francisco,1994.[ 9] 'Ullman, J. D. , Pr inciples of Database and Knowledge-Base Systems, Volume I, ComputerScience Press,New York, 1988.[ 10] UUllman, J. D. , Principles of Database and Knowledge-Base Systems, Volume II, ComputerScience Pr ess,New York, 1989.[ 11] UWiederhold, G. , Database Design, McGraw-Hill, New York , 1983.·71·
F ROM Movies m, m. stars s WHERE m.title= ″Casablanca″该查 询检索 Casablanca(《卡萨布兰 卡》) 中的 影星名。注 意 F ROM 子句中各 项的 顺序。首先, 我们通过说明m在Movie类的Movies范围中来定义m是Movie类的任 意对象。然后, 对于每个 m 值, 我们令 s 为电影 m 的影星集 m. st ars 中的一个 Star 对象。也就是, 我们考虑两个嵌套循环的所有(m,s) 对, 其中m是一部电影,s是该电影的影星。计算过程可以描述成: FORMovies中的每个m DOFOR m. stars 中的每个 s DOIFm.tit le= ″Casablanca″T HEN把 s. name 加到输出包中WHERE子 句 把 我 们 的 考 虑 范 围 限 制 到 一 些 对 中, 这 样 的 对 使m等 于 其 名 为Casabulanca(《卡萨布兰卡》) 的 Movie 对象。然后, SELECT 子句产生包( 在这种情况下它应该是一个集合) , 包中为满 足WHERE子句的(m,s) 对的影星对象s的 所有姓名属性。这些姓名是集合 mc. stars 中的影星名, 其中 mc 是电影对象 Casabulanca。8. 2. 5 消除重 复在技术上, 查询的结果更像例 8. 5 那样产生包, 而不是集合。也就是,OQL遵循SQL的默认: 如果不提出要求, 就不会消除结果中重复的部分。和 SQL 一样, 消除重复的 方法是在SELECT之后加上关键字DIST INCT( 互异) 。例 8. 6 让我们查询 Disney( 迪斯尼) 电影中的影星名。下面查询执行这样的操作: 当一个影星在几部Disney电影中出现时删除重名。 SELECT DIST INCT s. name FROM Movies m,m.starts s WHERE m. ownedBy. name = ″Disney″该查询的策略类似于例 8. 5。像例 8. 5 那样, 我们再一次考虑在两个嵌套循环中由一部电影和该电影的一个影星组成的所有的对。但是现在( m, s) 对的条件是:“Disney”是其studio对象为m.ownedBy的制片公司的名字。 □8. 2. 6 复杂的 输出类型SELECT 子句中 的表 达式不 必都 是简 单的变 量, 可以 是任何 表达 式( 包括用 类型 构造符构成 的表达式) 。例 如, 我们 可以用Struct类型构 造符构造几个表 达式, 并得到产 生结构集或者结构包的 select-from-where 查询。例 8. 7 假设我们要得到地址相同的影星 对的集合。我们可以 用如下查询来得 到该集合。 SELECT DIST INCT Struct(star1∶s1,star2∶s2) F ROM Stars s1, St ars s2 WHERE s1.addr=s2.addr AND s1.name<s2.name·503·
也就是, 我们考虑所有的影星对, s1 和 s2。WHERE 子句检验他们是否具有同一地址。它同时检验第一 个影星名按字母 顺序是否在第 二个影星 的前面, 所以 不会产生 由同一影 星重复两次组成的对, 并且也不会产生以两种不同顺序组成的同一影星对。经过这两种检验的每个对都产生一个记录结构。该结构类型是具有两个域的记录, 其域名为 st ar1 和 star2。由于为这两个域提供值的变量 s1 和 s2 的类型都是 Star 类, 因此每个域的类型也都是Star类。正式地说, 也就是对于某个名字N该结构的类型是Struct N {st ar1∶ Star, star2∶ Star}查询结果的类型是类型N这种结构的集合, 也就是:Set〈Struct N {star 1∶ Star , st ar2∶ Star}〉注意, 该查询 结果的类 型是 一种 类型的 实例, 它 可以在OQL程序 中出 现, 但不能 作为 属性或联系的类型在 ODL 说明中出现。 □顺便提 一下, 如果我们 在关键 字SELECT之后 只列出分 量和域名, 而 不显式 地定 义结构类型, 也可以得到和例 8. 7 一样的效果。也就是, 我们可以把例 8. 7 查询中的第一行改写成SELECT DIST INCT star 1∶ s1, star2∶ s28. 2. 7 子查询我们可以在适于聚集( 例如集合) 的任何地方使用select-from-where表达式。一个意想不到的地方是, 子查询可以出现在 FROM 子句中, 在该子句中, 作为变量范围的聚集可以通过select-from-where表达式生成。实际上, 按照SQL2 标准,FROM子句中允许有带括号的子查 询。顺便提一下, 同种 类型的 能力 —— 用表 达式来 定义表而 不是表名 —— 是SQL3 推荐标准的一部分, 并且在某些商业SQL系统中得到了应用。例 8. 8 让我们重做例 8. 6 的查询, 查询 Disney( 迪斯尼) 公司制作的电影中的影星。首先, 迪斯尼电影的集合可以通过下面的查询获得: SELECT m FROM Movies m WHERE m. ownedBy. name = ″Disney″我们现在可以用该查询作为子查询来定义代表迪斯尼电影的变量d所能覆盖的集合。 SELECT DIST INCT s. name FROM( SELECT mFROM Movies mWHERE m.ownedBy.name= ″Disney″)d, d. stars s查询“找出迪斯尼电影的影星”的这种表达式不比例 8. 6 更简洁, 可能更差。然而, 它确实说明了 OQL 中建立查询的新形式。在上面的查询中, FROM 子句具有两 个嵌套的循环。在第一个循环中, 变量d覆盖了所有的迪斯尼电影, 这是FROM子句中子查询的结果。对于 嵌 套 在第 一 个 循 环 外 的 第 二 个 循环, 变 量 s 覆 盖 电 影 d 的 所 有 影 星。注 意, 不 需 要WHERE子句。 □·603·
8. 2. 8 对结果 排序OQL 中 的 select-from-where 表 达 式 的 结 果 或 者 是 包, 或 者 ( 如 果 使 用 了DIST INCT) 是集合。如果我们在select-from-where后面使用ORDER BY子句, 就 可以使输出成为 列表, 并能 同时选择该列表 的元素顺序。OQL 中的 ORDER BY 子句非常 类似于SQL的同样的子句。关键字ORDER BY的后面跟着表达式的列表。根据查询结果中的每个对象计算其中第一个表达式的值, 然后把对象按该值进行排序。如果为等序, 那么它们就按第二个表达式的值进行排序, 接下来按第三个, 依此类推。例 8. 9 让我们找出迪斯尼电影的集合, 但让结果是按照长度排序的电影列表。如果为等序, 则让同等长度的电影按字母顺序排列。该查询是: SELECT m FROM Movies m WHERE m. ownedBy. name = ″Disney″ ORDER BY m.length,m.tit le前三行与例 8. 8 中的子查询相同。第四行规定 select-from-where 查询所产生的对象m, 首先按照m.length的值( 也就是电影的 长度) 排序, 然后, 如果为等 序, 就按m.title的值( 也就是电影名) 排序。于是该查询所产生的值是 Movie 对象的列表。默认的顺序为升序, 但 是 选 择 升 序 还 是 降 序 可 以 在ORDER BY子 句 的 末 尾 分 别 用 关 键 字ASC或 者DESC来指定, 这和 SQL 是一样的。 □8. 2. 9 本节练 习练习 8. 2. 1: 利用练习 8. 1. 1 和图 8. 2 中的ODL模式, 用OQL写出下列查询:* ( a) 找出价格在$2 000 以下的所有 PC 产品的型号。 (b) 找出RAM至少 32M字节的所有PC产品的型号。* ! ( c) 找出至少制造两种不同型号激光打印机的制造商。 (d) 某个PC或者便携式电脑有r M字节RAM和h G字节硬盘, 找出这样的(r,h) 对的集合。 (e) 按照处理器速度的升序建立PC( 对象, 不是型号) 的列表。 ! ( f) 按照屏 幕尺寸的 降序, 建 立至少 有 16M 字 节 RAM 的便 携式 电脑的 型号 的列表。! 练习 8. 2. 2: 重做练习 8. 2. 1, 在每个查询中至少有一个子查询。练习 8. 2. 3: 利用练习 8. 1. 2 和图 8. 3 中的ODL模式, 用OQL写出下列查询: ( a) 找出至少有 9 门火炮的舰艇的等级名。 (b) 找出至少有 9 门火炮的舰艇( 对象, 不是舰艇名) 。 ( c) V找出排水量在 30 000 吨以下的舰艇名。结果首先按照最早的下水年份排成列表, 如果为等序, 再按照舰艇名的字母顺序排列。 ( d) \找出是姊妹舰艇( 也就是, 同等级的舰艇) 的对象对。注意, 要的是舰艇本身, 而不是舰艇的名字。·703·
! ( e) 找出至少有两个不同国家的舰艇沉没的战役的名字。! ! (f) 找出所列出的舰艇没有损坏的战役的名字。8. 3 OQL表达式的附加格式在这一节将会 看到,OQL提供给我们用来 建造表达 式的, 除了select-from-where之外, 还 有 其 他 一 些 运 算 符。这 些 运 算 符 包 括 逻 辑 量 词 ( 全 称 (for-all) 和 存 在 (there-exists) ) 、聚合运算符、分组( group-by) 运算符和集合运算符( 并、交和差) 。8. 3. 1 量词表 达式我们可以检测是否所有的集合成员或至少有一个集合成员满足某个条件。为了检测是否集合S的所有成员都满足条件C(x) ( 其中x是变量) , 我们使用OQL表达式FOR ALL x IN S∶ C( x)如果S中 的 每 个x都 满 足C(x) , 则 该 表 达 式 的 结 果 就 为 真 (T RUE) , 否 则 为 假(FALSE) 。类似地, 如果 S 中至少有一个 x 使 C( x) 为真( TRUE) , 则表达式EXIST S x IN S∶C(x)为真(T RU E) , 否则为假(FALSE) 。例 8. 10 表达“找出迪斯尼电影的所有影星 ”这一查 询的另一种方法 如图 8. 5 所示。在这里, 我们把注意力集中在影星s上, 查询s是否是某个迪斯尼电影m中的影星。3) 行考虑电影集合s.starredIn( 它是影星s出演的电影的集合) 中的所有电影m。然后, 4) 行查询电影 m 是否是迪斯尼电影。我们即使找到一部这样的电影, 则 3) 和 4) 行中的 EXIST S表达式的值就为T RUE, 否则为FALSE。 □例 8. 11 让我们用全称(for-all) 运算符写一个查询来查找只在迪斯尼电影中出现的影星。在技术上, 该集合包括电影中根本没出现( 仅就我们的数据库而言) 的影星。可以把另一种情况加到我们的查询中, 即要求影星至少在一部电影中出现, 但是我们把该改进留作练习。图 8. 6 显示了该查询。 □ 1) SELECT s2) FROM Stars s3) WHE RE EXIST S m IN s. starredIn:4) m. ownedBy. name = ″Disney″ 图 8. 5 使用存在量词的子查询 1) SELECT s2) FROM Stars s3) WH OERE FOR ALL m IN s. starredIn :4) m. ownedBy. name = ″Disney″图 8. 6 使用全称量词的子查询8. 3. 2 聚合表 达式OQL 使用与 SQL 相同的五种聚合运算符: AVG, COUNT , SUM, MIN 和 MAX。但是可以认为SQL中的这些运算符 是应用于表的指 定列, 而OQL中的同样运 算符则应 用于其成员为合适类型的聚集。也就是, COU NT 可以应用于任何聚集; SU M 和 AVG 可以用于 算术( 例如整 数) 类型的聚 集;MIN和MAX可以 用于任 何可 比较的 类型 ( 例 如算 术·803·
值或者字符串) 的聚集。例 8. 12 为了计 算所有 电影的平 均长度, 我们 需要为所 有电影的 长度 建立一 个包。注意, 我们不应要电影长度的集合, 否则具有相同长度的两部电影将作为一部统计。该查询为:AVG(SELECT m.length FROM Movies m)也就是, 我们利用子查询从电影中提取长度分量。它的结果是电影长度的包, 然后我们将AVG运算符应用于该包从而得到需要的结果。 □8. 3. 3 分组表 达式SQL 中的 GROUP BY 子句也用在 OQL 中, 但是具有有趣的新意。OQL 中 GROUPBY子句的格式是:1. 关键字 GROUP BY。2. 用逗号分开的一个或者多个分区属性的列表。每个分区属性组成如下:( a) 域名 f,(b) 冒号,( c) 表达式 e。也就是,GROUP BY子句的格式为:GROUP BY f1∶e1 ,f2∶e2 , …,fn∶enGROUP BY 子句跟在 select-from-where 查询后面。表达式e1 ,e2 , …,en可以引用FROM子句中提到 的变量。为了便于理 解GROUP BY是如何 工作的, 我 们将FROM 子 句 限 制在 只 有 一 个 变 量 x 的 一 般 情 况。x 的 值 覆 盖 某 个 聚 集 C。 对 于 满 足WHERE子句 条件 的C的每个 成员 ( 记 为i) , 我 们 求跟 在GROU P BY之 后 的所 有表 达式, 以获得 e1( i) , e2( i) , …, en( i) 的值。这些值的列表就是值 i 所属的组。GROUP BY 实际的返回值是结构的集合。该集合的成员具有如下形式:Struct(f1∶v1 ,f2∶v2 , …,fn∶vn ,partition∶P)前n个域代表组。也就是,v1 ,v2 , …,vn 是值的列表, 该列表是根据聚集C中至少有一个满足 WHERE 子句条件的 i 值计算 e1 ( i) , e2 ( i) , …, en ( i) 得来的。最后一个域有一个特殊的名字分区(partit ion) 。直观看来, 分区的值P就是属于该组的 i 值。更确切地说, P 是如下格式的结构组成的包,Struct(x∶i)其中 x 是 F ROM 子句中的变量。具 有GROUP BY子 句 的select-from-where表 达 式 的SELECT子 句 可 能 只 引 用GROUP BY结果中的域, 称为f1 ,f2 , …,fn 和part ition。通过partition, 我们可以引用域x, x 出现在作为结构的包 P( 形成 partit ion 的值) 的成员中。这样, 我们就可以引用出现在FROM子句中的变量x, 但是也许只能在聚合运算符( 聚合了包P的所有成员) 中这么做。例 8. 13 让我们为每个制片公司和每年制作的电影建立电影总长度表。在 OQL 中,我们实际建立的是结构的包, 每个包有三个分量 —— 制片公司、年份和该制片公司这一年·903·
制作的电影总长度。该查询如图 8. 7 所示。 SELECT std,yr,sumLength∶SUM( SELECT p.m.lengthFROM partition p)FROM Movies mGROU P BY std∶m.ownedBy.name,yr∶m.year图 8. 7 按制片公司和年份将电影分组为了理解该 查询, 我们 先从 F ROM 子句开始。我们发现, 变量 m 覆盖 所有的 Movie对象。在这里m起到在通常讨论中x的作用。GROUP BY子句中有两个域std和yr, 它们分别对应于表达式 m. ownedBy. name 和 m. year。例如,Pretty Woman是迪斯尼在 1990 年制作的一 部电影。当m是这部电影的 对象时,m.ownedBy.name的值是″Disney″, 同时m.year的值是 1990。结果GROUP BY子句构造的集合的每个成员都有如下结构Struct(std∶ ″Disney″,yr∶ 1990,partition∶P)其中 P 是结构的集合。它包括结构:Struct( m∶ mp w)其中 mpw 是 Pretty Woman 的 Movie 对象。对于 1990 年迪斯尼的所有其他电影, P 中也都是域名为 m 的单分量结构。现在, 我们检查SELECT子句。作为GROUP BY子句的结果得到一个集合, 而对于该集合中的每个结构, 我们在查询结果的包中建立一个结构。第一个分量是 std。也就是域名为std, 它的值是从GROUP BY得到的结构的std域的值。与此类似, 结果的第二个分量具有域名yr并且其值与GROUP BYD结果中的yr分量相同。每个输出结构的第三个分量是:SUM(SELECT p.m.length FROM partition p)为了理解该 select-from-where 表达式, 我们首先要认识 到变量 p 覆 盖 GROUP BY 结 果结构的part ition域的成员。回忆一下,p的每个值都是Struct(m∶o) 形式的结构, 其中o是电影对象。因此, 表达式 p. m 引用该对象 o。于是, p. m. lengt h 引用该 Movie 对象的长度分量。作为结果, 该select-from表达式按特定组产生电影长度的包。例如, 如果std具有值“Disney”并且 yr 具有值 1 990, 那么 select-from 的结果就是 1990 年迪斯尼所制作的电影长度的包。当我们把SU M运算符用于该包时, 就得到该组的电影长度的总和。因此, 如果1 234 是 1 990 年所有的迪斯尼电影的正确的总长度, 那么输出包中的一个结构就会是:Struct( std∶ ″Disney″, yr∶ 1 990, sumLengt h∶ 1 234) □万一FROM子句不只有一 个变量, 对该 查询的解 释做一些 改变是 必要的, 但是原 则上和上面所描述的单变量情况是一样的。假定出现在 FROM 子句中的变量是 x1, x2, …,xk 。那么:1. 所有变量x1 ,x2 , …,xk 都可能用在GROUP BY子句的表达式e1 ,e2 , …,en 中。2. 作为partit ion域值的包, 其中的结构具有名为x1 ,x2 , …,xk 的域。3. 假定 i1 , i2 , …, ik 分别是 变量 x1 , x2 , …, xk 的值, 并使得 WHERE 子句为 真, 那 么·013·
作为 GROUP BY 的结果得到的集合中就有一个结构, 其格式如下:Struct(f1∶e1 (i1 , …,ik ) , …,fn∶en (i1 , …,ik ) ,part ition∶P)而在包 P 中的结构是Struct(x1∶i1 ,x2∶i2 , …,xk∶ik )8. 3. 4 HAVING子句OQL的GROU P BY子句 后 面可 以 跟 着HAVING子 句, 它 的 含义 类 似 于SQL的HAVING 子句。也就是形如HAVING〈条件〉的子句用来删除由 GROUP BY 建立的某些组。该条件用于 GROUP BY 结果的每个结构的partition域值。如果为 真, 那么 该结构就像 8. 3. 3 节中那样传给输 出来处理。如果 为假, 那么该结构就不用来作为该查询的结果。例 8. 14 让我们重复例 8. 13, 但是只查询在某年至少制作了一部超过 120 分钟电影的制片公司在该年制作的电影长度之和。图 8. 8 中的查询完成这项工作。注意在HAVING子句中, 我们使用和 SELECT 子句中一样的查询以便得到给定的制片公司和年份的电影长度的包。在HAVING子句中, 我们拿这些长度中的最大值和 120 相比较。 □ SELECT std, yr , sumLength∶ SUM( SELECT p. m. lengthFROM partition p)FROM Movies mGROU P BY std∶m.ownedBY.name,yr∶m.yearHAVING MAX( SELECT p. m. length FROM partition p) > 120图 8. 8 限制所考虑的组8. 3. 5 集合运 算符我们可以把并、交和差运算符用于类型为集合或者包的两个对象上。像SQL那样, 这三个运算符分别用关键字UNION,INT ERSECT和EXCEPT来表示。 1) (SELECT DESTINCT m2) FROM Movies m, m. stars s3) WHERE s.name= ″Harr ison Ford″)4) EXCE LPT5) (SELECT DISTINCT m6) FROM Movies m7)WHERE m.ownedBy.name= ″Disney″)图 8. 9 使用两个集合的差集的查询例 8. 15 我们 可以通过图 8. 9 中的两 个select-from-where查 询之间的 差集找出 不是迪斯尼公司制作的但是哈里森·福特( Harrsion Ford) 主演的电影集合。1) 到 3) 行找出哈里森·福特(Harrsion Ford) 主演的电影集合, 而 5) 到 7) 行则找出迪斯尼公司制作的电影集合。4) 行中的EXCEP T执行两者的差。 □·113·
我们应当注意到图 8. 9 中的 1) 和 5) 行中的关键字 DIST INCT 。该关键字使这两个查询的结果成为集合类型; 没有DISTINCT, 该结果将成为包( 多集) 类型。在OQL中, 运算符 U NION, INT ERSECT 和 EXCEPT 可以对集 合或包进行运算。当两 个参数都是集 合时, 这些运算符就具有它们通常的集合含义。然而, 当两个参数的类型 都是包, 或者一 个是包而 另一个是 集合时, 运算 符就用包 的含义。回顾一下 4. 6. 2 节, 包允许一个对象在其中出现任意次。假定B1 和B2 是两个包,而 x 是在 B1 中出现 n1 次并在 B2 中出现 n2 次的对象。n1 或者 n2 , 或者二者都可以是 0。包的运算规则如下:· 在 B1 ∪B2 中, x 出现 n1 + n2 次。· 在B1 ∩B2 中,x出现min(n1 ,n2 ) 次。· 在 B1 —B2 中,1. 如果n1 ≤n2 , 那么x出现 0 次。2. 如果 n1 > n2 , 那么 x 出现 n1 - n2 次。对于图 8. 9 中的特定查询, 一部电影在每个子 查询的结果中出 现的次数是 0 或 者 1, 所以不管是否使用 DIST INCT 结果都一样。但是结果的类型不同。如果使用了 DISTINCT , 那么结果 的类型就 是Set<Movie> , 但 如果在一 处或 者两 处省略 了DIST INCT, 那 么结 果的类型就是 Bag< Movie> 。8. 3. 6 本节练 习练习 8. 3. 1: 利用练习 8. 1. 1 和图 8. 2 中的ODL模式, 用OQL写出下列查询:* ( a) 找出同时制造 PC 和打印机的制造商。* (b) 找出只制造PC且硬盘容量至少为 2G字节的PC制造商。 ( c) 找出制造 PC 但不制造便携式电脑的制造商。* (d) 找出PC的平均速度。* ( e) 对于每个 CD 速度, 找出 PC 中 RAM 的平均容量。! (f) T找出这些制造商, 他们制造的某个产品具有至少 16M字节的RAM, 并且还制造一种价格在$1 000 之下的产品。! ! (g) g对于那些所制造的PC的平均速度至少为 150 的制造商, 找出他 们提供的PC中 RAM 的最大容量。练习 8. 3. 2: 利用练习 8. 1. 2 和图 8. 3 中的ODL模式, 用OQL写出下列查询: ( a) 某些等级的所有舰艇都在 1919 年以前下水, 找出这些舰艇的等级。 (b) 找出任何等级的最大排水量。 ! ( c) 对于每种火炮的口径, 找出装备了该火炮的舰艇下水的最早年份。* ! ! (d) 对于至少有一艘舰艇在 1919 年以前下水的每个舰艇等级, 找出战役中沉没的该等级舰艇数目。 ! (e) 找出一个等级舰艇的平均数目。 ! ( f) 找出舰艇的平均排水量。 ! ! (g) 找出至少有一艘来自大不列颠(Great Brtain) 的舰艇参战, 并且至少有 两艘·213·
舰艇沉没的战役( 对象, 不是名字) 。! 练习 8. 3. 3: 我们在 例 8. 11 中 提到, 图 8. 6 中的OQL查询将 返回这 样的影星: 完全 没有主演过其他电影, 因此,“只是专门出现在迪斯尼电影中”。重写该查询以返回至少主演一部电影的影星和他们出演的所有迪斯尼电影。8. 4 OQL 中对象的赋值和建立在这一节我们将考虑 如何把OQL和它的宿 主语言相连, 在我们的实例 中将用C++作为宿主语言, 不过某些系统中可能用另一种面向对象的通用编程语言作为宿主语言。8. 4. 1 对宿主 语言变量 赋值SQL需要在元组分量和宿主语言变量之间传递数据, 而OQL与之不 同, 它很自 然地适合于它的宿主语言。也就是, 我们学过的 OQL 的表达式( 比如 select-from-where) 在产生对象的同 时产生值。可以把这 些OQL表达式的结果 值赋给任何合适类 型的宿主语 言变量。例 8. 16 OQL表达式 SELECT DIST INCT m FROM Movies m WHERE m. year < 1920产生 1920 年以前制作的所有电影的集合。它的类型是Set<Movie> 。如果oldMovies是同类型的宿主语言变量, 那么我们就可以写( 用扩充了 OQL 的 C++ ) : oldMovies= }SELECT DIST INCT mFROM Movies mWHERE m.year< 1920并且 old Movies 的值将成为这些 Movie 对象的集合。 □8. 4. 2 从聚集 中提取元 素既然select-from-where和group-by表达 式都 产 生聚 集—— 集 合或 者包, 如果 我 们想得到该聚集中的单个元素, 就必须做一些额外的工作。即使可以确信某个聚集只包含一个元素, 这句话也是正确的。OQL提供了运算符ELEMENT用于把单独的集合或者包转换成它的单个成员。例如, 该运算符可以用于已知返回单个值的查询结果。例 8. 17 假 定我 们 想 把 代 表电 影《乱 世 佳人 》(Gone With the Wind) 的 对 象 赋 给Movie 类型( 也就是, Movie 类是它的类型) 的变量 gwtw。查询 SELECT m F ROM Movies m WHERE m.title= ″Gone With the Wind″的结果是仅仅包含这样一个对象的包。如果把该包直接赋给变量 gwtw, 将得到一个类型错误, 因此不能这样做。然而, 如果我们首先应用ELEMENT运算符:·313·
gwtw = ELEMENT ( SELECT mFROM Movies mWHERE m.tit le= ″Gone With the Wind″) ;那么变量和表达式的类型就匹配了, 同时赋值也是合法的。 □8. 4. 3 获取聚 集的每个 成员获取集合或者包的每个成员是比较复杂的, 但是仍然比SQL中需要基于游标的算法简单。首先, 我们需要把集合或者包转换为列表。我们用带ORDER BY的select-from-where表达式来做这件事。回忆一下 8. 2. 8 节, 这种表达式的结果是所选择的对象或值的列表。例 8. 18 假 定 我 们 想 要 类Movie的 所 有 电 影 对 象 的 列 表。 由 于 (title,year) 是Movie的键码, 因此我们可以利用电影的名称(tit le) 和( 为把等序分开) 年份(year) 。语句movieList = SELECT mFROM Movies mORDER BY m. title, m. year;将把按名称和年份排序的所有Movie对象的列表赋给宿主语言变量movieList。 □我们一旦得到了一个列表, 不管是排序的还是没有排序的, 就可以用序号访问每个元素; 列表L的第i个元素可以用L[i- 1] 得到。注意, 假定列表和数组的序号从 0 开始, 这与C或C++ 一样。例 8. 19 假定我们想写一个C++ 函数来打印每部电影的名称、年份和长度。该函数的描述如图 8. 10 所示。 ?1) movieLis Yt = SELECT mFROM Movies mORDER BY m.title,m.year;2) number OfMovie=COU NT(Movies) ;3) for( i= 0;i<numberOfMovies;i+ + ) {4) movie = movieList[ i] ;5)coutV< <movie.title< < ″″< <movie.year< < ″″6) < <movie.length< < ″\n″;7) }图 8. 10 检查并打印每部电影1) 行对 Movie 类进行排序, 把结果放到变 量 movieList , 它的 类型是 List< Movie> 。2) 行用 OQL 运算符 COUNT 计算电影的数 目。其次, 3) 到 7) 行是 for 循 环, 在该循 环中整数变量i覆盖了该列表的每个位置。为了方便, 把列表的第i个元素赋给变量movie。然后, 5) 和 6) 行将打印电影的相关属性。 □8. 4. 4 建立新 对象我们已经看到像select-from-where这样的OQL表达式允许我们建立新的对 象。这些对象通过对已有对象的计算来建立。通过把常量和其他表达式显式地组合到结构和聚·413·
第 2 章 数 据 库 建 模 设计数据库的过程首先是分析数据库必须保留什么信息以及该信息各成份之间的联系。数据库的结构, 称为数据库模式, 通常用适合表达这种设计的某种语言或表示法加以说明。经过适当的考虑之后, 把设计固定为一种格式, 再把按这种格式进行的设计输入到数据库管理系统中, 这时数据库就有了具体的存在形式。在 这本 书 中, 我们 将使 用两 种设 计 表示 法。比 较 传统 的 方法 称 为实 体-联 系(E/R,Ent ity-Relat ionship) 模型, 具有图的特性, 用方框和箭头表示基本的数据元素和它们 之间的连接。同时我们将介绍ODL( 对象定义语言,Object Definition Language) , 它是面向对象的数据库设计方法, 是面向对象的数据库系统正在形成的标准。本章还介绍另外两种模型: 网状模型和层次模型, 这主要是对过去的关注。在某种意义上, 它们是ODL的范围有限的版本, 已经用在 70 年代实现的商业数据库系统中。在第 3 章, 我们把注意力转向关系模型。关系模型用表的聚集来描述世界, 因而其表达力多少受到些限制。然而, 这个模型非常简单实用, 是现行主要的商业数据库管理系统的基础。通常, 数据库设计者首先使用E/R模型或基于对象的模型建立模式, 然后将其转换成关系模型, 以利于实现。图 2. 1 给出了设计的过程。首先是对信息建模的想法。这些想法可以通过某种设计图 2. 1 数据库建模和实现的过程语言加以表达。E/ R 和 ODL 是两种可供选择的方法, 当然还有其他的方法。在 大多数情况下, 设计将用关系数据库管理系 统来实现。这时通过完全机械的过程( 我们 将在第3 章 讨论 ) , 就 可 把 抽象 的 设计 转 换成 具 体的关系数据库实现。我们还展示了另一种可供选择的方法, 在这里, ODL 的 设计成 为面向对象 数据库 管理系统 的输入。在 这种情况 下, 转换是相 当自动化 的, 可能涉 及从ODL语句向相应的面向对象编程语言( 例如 C+ + 或 Smallt alk) 的语句的简单转换。2. 1 ODL介 绍ODL ( 对象定 义语言) , 是用面向 对象的术语 ( 就 像人们在 诸如 C+ + 或 Smalltalk 之类的 语 言 中 遇 到 的 那 样) 说 明 数 据 库 结 构 的 推 荐 的 标 准 语 言。 它 是IDL(InterfaceDefinition language, 接 口 定 义 语 言 ) 的 扩 展, 又 是 CORBA ( Common Object RequestBroker Architecture, 公用对象请求代理程序体系结构) 的一个组件。CORBA是正在制订的分布·81·
集中来建立对象也是可能的。我们用一种显而易见的方式把类型构造符用于值来做到这一点。当我们建立值时, 不用描述类型的尖括号, 而是用圆括号。我们在例 8. 7 就看到了这种约定的例子。在例 8. 7 中, 语句SELECT DIST INCT Struct(star1∶s1,st ar2∶s2)用来规定 查 询 的 结 果 是 一 个 对 象 的 集 合, 对 象 的 类 型 是 Struct {star1∶ St ar, star2∶Star}。我们给出域名st ar1 和star2 来规定该结构, 而这些域的类型则可以从变量s1 和s2的类型推断出。例 8. 20 通过 把任何一种 聚集类 型构造符Set,Bag,List或Array用于 同种类型 的对象, 我们也可以建立聚集。例如, 考虑下列赋值序列: x=Struct(a∶ 1,b∶ 2) ; y = Bag( x, x, Struct( a∶ 3, b∶ 4) ) ;第一行赋给变量x Struct( a∶ integer, b∶ integer)类型的值, 该类型具有两个名为a和b的整型域。我们可以把这种类型的值看成元组, 这些元组只是用这两个整数而不是域名 a 和 b 作为分量。于是, x 的值可以用( 1, 2) 来表示。第二行把y定义成一个包, 它的成员是和上面的x类型相同的结构。( 1, 2) 组成的对 在该包中出现了两次, 而( 3, 4) 出现了一次。 □如果我们有某 种类型而某个查 询将产生 该类型的 对象聚集, 那 么就可以 用该类型 名来代替显式的类型表达式。例如, 我们在例 8. 7 清楚地看到如何建立影星对的集合。我们在关键字SELECT之后跟着一个表达式, 该表达式使用类型构造符Struct来建立包括两个域的对象, 而这两个域的值均为影星对象。现在假定我们已经定义了类型StarPairs为:St ruct{star1∶ Star, star2∶ Star }然后可以重写例 8. 7 中的查询以便在下面的SELECT子句中使用该类型。 SELECT DIST INCT StarPairs( st ar1∶ s1, star 2∶ s2) FROM Stars s1,St ars s2 WHERE s1. addr = s2. addr AND s1. name < s2. name与例 8. 7 的唯一的区别是该查询的结果具有类型Set〈St arPair〉。因此可以把该结果赋给已说明为该类型的宿主语言变量。当类型名是类时将类型名作为参数特别有用。类一般有几种不同形式的构造函数, 这取决于显式的初始化的特性和给定的某种默认值。例如, 方法肯定不初始化, 大多数属性将得到初始值, 而联系可能初始化为空集以后再添充。每个构造函数名都是类名, 它们通过参数中提到的域名来区别。如何定义这些构造函数其细节取决于宿主语言。例 8. 21 让我们考虑Movie对象的一个可能的构造函数。我们假定该函数利用属性title, year , length 和 ownedBy 的值, 产 生在所列 出的域 具有这 些值的 对象 以及影 星的 空集。那么, 如果mgm是值为米高梅(MGM)St udio对象的变量, 我们就可以用如下语句来建立一个《乱世佳人》( Gone With the Wind) 对象: gwt w=Movie( title: ″Gone With the Wind″,·513·
year: 1939,lengt h: 239,ownedBy: mgm) ;该语句有两个作用:1. 建立一个新的 Movie 对象, 该对象将成为 Movies 范围的一部分。2. 使该对象成为宿主语言变量gwt w的值。 □8. 4. 5 本节练 习练习 8. 4. 1: 将下列常数赋给宿主语言变量 x:* (a) 集合{1, 2, 3}。( b) 包{1, 2, 3, 1}。(c) 列表( 1, 2, 3, 1) 。( d) 结构的第一个分量, 名为 a, 是集合{1, 2}, 而第二个分量名为 b, 是包{1, 1}。(e) D结构的包, 每个结构都有名 为a和b的两个 域。包中三个结构 各自成对, 其值为( 1, 2) , ( 2, 1) 和( 1, 2) 。练习 8. 4. 2: 利用练习 8. 1. 1 和图 8. 2 中的ODL模式, 写出用OQL扩充了 的C++ ( 或者你选择的一种面向对象的宿主语言) 语句来完成下列操作:* (a) 把型号为 1 000 的PC对象赋给宿主语言变量x。 ( b) 把至少有 16M 字节 RAM 的所有便携式电脑对象的集合赋给宿主语言变量 y。 (c) 把售价低于$1 500 的PC的平均速度赋给宿主语言变量z。! ( d) j找出 所有的 激光 打印机, 打 印它 们的型 号和 价格 的列表, 并 随后 给出 信息 指明价格最低的型号。! ! ( e) 打印一个表, 对于每个 PC 制造商, 给出 PC 的最低和最高价格。练习 8. 4. 3: 在该练习中, 我们将利用练习 8. 1. 2 和图 8. 3 的ODL模式。假定对于该模式的 4 个类中的每一个都有一个同名的构造函数, 它为每个属性和单值联系取值, 但是不为多值联系取值( 它们将初始化为空) 。对于其他类的单值联系, 你可以设定一个其当前值是相关对象的宿主语言变量。建立下列对象, 并且在每种情况下, 把该对象赋给宿主语言变量作为它的值。* ( a) Maryland 等级的战列舰 Colorado, 1923 年下水。(b)Lüt zow等级的战列舰Graf spee, 1936 年下水。( c) Malaya 战役的结局是战列舰 Prince of Wales 沉没。(d)Malaya战役是 1941 年 10 月 10 日开战的。( e) Hood 等级的大不列颠巡洋舰具有 8 门 15 英寸的火炮和 41 000 吨的排水量。8. 5 SQL3 中的元组对象OQL 中没有明确的关系概念; 它只是结构的集合( 或者包) 。然而, 在 SQL 中, 关系的概念是如此 重要以致于SQL3 中的对象 仍把关系作为核 心概念。SQL3 中的对象来自 两·613·
种风格:1. 行对象(Row Object) , 它们基本上是元组;2. 抽象数据 类型( Abstract Dat a T ype 通常 缩写为 ADT , 或在某 些 SQL3 文档中 缩写为值ADT) , 它们是只能用来作为元组分量的一般对象。我们将在本节介绍行对象, 在 8. 6 节介绍 ADT 。8. 5. 1 行类型在SQL3 中, 人们可以定义元组类型, 该类型大致相似于对象的类。行类型说明 组成如下:1. 关键字 CREAT E ROW T YPE,2. 类型的名字, 和3. 用括号括起的属性及其类型的列表。也就是, 行类型T定义的格式是: CREAT E ROW T YPE T (〈分量说明〉)例 8. 22 我们 可以建立代 表电影影 星的行类 型, 类似于图 8. 4 的OQL例子中给 出的 Star 类。然而, 我们不能直接把电影集合表示成 Star 元组的域。于是, 我们只能从 Star元组的分量name和address开始。首先, 注意图 8. 4 中的地址类型本身是具有分量street和city的元组。这样我们需要两个类型定义, 一个为地址, 而另一个为 影星。SQL3 中允许用行类 型作为另一个行 类型或关系的分量类型。图 8. 11 给出了必要的定义。类型 AddressT ype 的元组 具有两个 分量, 它们的 属性是 street 和 city。这些 分量 的类型是长度分别为 50 和 20 的字符串。类型St arType的元组同样具有两个分量。第一个是属性 name, 它 的 类 型 是 30 个 字 符 的 字 符 串, 第 二 个 是 address, 它 的 类 型 是AddressT ype, 也就是, 它是具有分量st reet和city的元组。 □ CREATE ROW TYPE AddressT ype(street CHAR( 50) ,city CHAR( 20)) ; CREATE ROW TYPE StarType(name CHAR ( 30) ,address Addr essType) ;图 8. 11 两个行类型定义8. 5. 2 说明具 有行类型 的关系说明了行类型之后, 我们可以说明其元组属于该类型的一个或者多个关系。关系说明的格式和 5. 7. 2 节的一样, 但是我们用·713·
OF TYPE〈行类型名〉来代替标准SQL的表说明中属性及其类型的列表。例 8. 23 我们可以用CREAT E T ABLE MovieStar OF T YPE St arT ype;说明 MovieSt ar 是其 元组 类型 为 StarT ype 的关系。 结果, 表 MovieStar 具 有 两个 属性,name和address。注意 后者的类型本身 是行类型, 这是SQL3 以前 的SQL标 准通常所 不允许的。 □尽管每个行类 型一般都对应一 个关系, 并且把 该关系看 成对应 于该元组 类型的类 的范围( 按 8. 1. 3 节的含义) , 但是对于给定的行类型, 允许有多个关系或者没有关系。8. 5. 3 访问行 类型的分 量由 于SQL3 中 分 量 本 身 具 有 结 构, 因 此 我 们 需 要 一 种 方 法 来 访 问 分 量 中 的 分 量。SQL3 使用双点表示法, 它严格地对应于 OQL 或者 C 中的单点表示法。例 8. 24 图 8. 12 的查询找出住在Beverly Hills的每个影星的姓名和街道地址。我们选择了全属性名 MovieSt ar . name 和 MovieSt ar . address 来说明单 点和双点之间的 差别。然而, 既然name和address在这里是非二义性的属性,MovieSt ar和单点就不需要了。 □SELECT MovieStar.name,MovieStar.address. .streetFROM MovieStarWHERE MovieStar . address. . city = ′Beverly Hills′图 8. 12 范围分量的分量8. 5. 4 引用在 SQL3 中, 面向对象语言的对象标识的作用通过引用( reference) 的概念来获得。行类型 的分 量可 以把对 另一 个行类 型的 引用 作为它 的类 型。如 果T是 行类 型, 那 么REF( T ) 就是对类型为 T 的元组的引用类型。如果我们把元组作为对象, 那么对该对象的引用是它的对象标识(ID) 。例 8. 25 在 MovieStar 中我们还不 能记录影星主演 的所有电影的集 合, 但是可 以记录他们最好的电影。首先我们需要说明Movie关系, 如果愿意的话, 还可以同时为该关系说明行类型。不包括与影星、制片公司、制片人的重要联系的简单电影类型如下: CREAT E ROKW T YPE MovieT ype(tit le CHAR( 30) ,year INT EGER,inColor BIT ( 1) ) ;然后我们可以用CREAT E T ABLE Movie OF T YPE MovieT ype;来说明其为上述元组类型的关系。·813·
接下来, 我们必须更改 MovieStar 元组的类型以 包括对该 影星的 最好电影 的引用。①StarT ype的新定义如下: CREAT E ROW T YPE StarT ype(name DCHAR( 30) ,address AddressT ype,bestMovie REF(MovieT ype)) ; □例 8. 26 然而, 假定我 们要求 在电影和 影星之 间有标准 的多对多 的联 系: 一 个影 星处于电影集合中, 而一部电影有影星集合。虽然 ODL 允许影星集合作为电影的分量而且反之亦然, 但是SQL3 保持了贯穿 全书所遵循的有 关方法。②然而, 多对多的 联系可以 用包含成对的相关项的独立关系来表示。图 8. 13 提示我们如何表示电影和影星之间的 star-in 联系。在 SQL3 以前的标准中, 只能通过有关类成对的键码表示多对多的联系,SQL3 允许我们通过具有引用类型的属性直接 引 用 对 象 ( 确 切 地 说 是 元 组 ) 。 我 们 先 分 别 定 义 关 系Movie和MovieStar的 类 型MovieT ype 和 StarT ype。我们已经回到最初的没有最好电影的行类型 StarT ype。为关系StarsIn 定义的行类型 StarsInType 含有成对的引用; 每对引用一个影星和该影星出演的一部电影。 CREVATE ROW TYPE MovieType(title CHAR( 30) ,year INTEGER,inColor BIT ( 1)) ;CRE VATE RO W TYPE AddressType(street CHAR( 50) ,city CHAR( 20)) ;CRE VATE ROW TYPE StarType(name :CHAR ( 30) ,address AddressType) ;CREVATE ROW TYPE StarsInType(star REF( StarType) ,movie REF( MovieType)) ;CREATE TABLE Movie OF T YPE MovieT ype;CREATE TABLE MovieStar OF TYPE StarType;CREATE TABLE StarsIn OF TYPE StarsInType;图 8. 13 影星、电影及其联系·913·①②尽 管SQL3 标 准的 某些草 案确 实允许 聚集类 型( 例如 , 集 合或 者关系 ) 作为 属性 的类 型, 但 是很 可能 把这 种聚集类型 的使 用推迟 到以后 的 SQ L4 标准 中。SQ L3 不 包括A LT E R T YP E或 者类似 语句 , 这 类语句 允许 我们更 改已 有的类 型定义 。这 样, 如果 我们希 望更改以前 定义 的行类 型, 实际 上将不 得不 撤消行 类型和 定义 成具有 该类 型的任 何表 , 然 后重新 定义 类型和 重新构 造表 。
域和行类型在 5. 7. 6 节我们学习了域, 它是一种类型说明。在域和行类型之间至少有两个重要的差别。首先, 一个明显的差别是域定义分量的类型, 而行类型是整个元组的类型。但是还有一个比较细微的差别。域是简写形式(shorthands) 。两个域可以表示同一类型, 这些域的值将不予区分。然而, 假设两种行类型T1 和T2 具有相同的定义。结果具有这两 种类型的两个关 系的元组并不能 互换。例如, 某属 性的类型 是对 T 1 的引用, 而该属性并不能引用类型是 T 2 的元组。行类型的定义后面跟着使用这些行类型的三个表 Movie、MovieSt ar 和 StarsIn 的说明。注意, 行类型AddressT ype没有用来作为表的类型。而是把它 作为行类型StarT ype的属性address的类型。我们将比较在这里称为 StarsIn 的关系和 3. 9 节数据库模式中的同名关系。后一个关系具 有属 性movieTitle和movieYear而不 是对 电影 元 组的 引 用, 还具 有 属性starName以代替对影星元组的引用。 □8. 5. 5 利用引 用一旦我们 认为一个元组的 分量可以是其 他某个( 或同 一) 关 系的引 用, 那 么通过提 供引用运算符来扩展SQL就是很自然的。SQL3 中用- > 符号 表示引用, 它与C中的 该运算符具有同样的含义。也就是, 如果x是元组t的引用, 并且a是t的属性, 那么x- >a就是 元 组 t 中属 性 a 的 值。该 运 算 符 在许 多 SQL3 查询 中 是 很 方 便的, 因 为它 可 以 代 替SQL2 中某些必要的连接。例 8. 27 让我们利用图 8. 13 的 模式, 找出 Mel Gibson 主演 的所有电影的名 称。我们的策略是检查 St arsIn 中的每个对。如果所引用的影星是 Mel Gibson, 那么我们就把该对的另一个分量所引用的电影名作为结果的一部分。该SQL3 查询是: SELECT movie- > title FROM StarsIn WHERE st ar- >name=' Mel Gibson';对该查 询的解 释如下。对 于 SQL 的 所有 select-from-where 查询, 我 们考 虑 F ROM子句中提到的关系的每个元组, 比如说(s,m) 。其中s是对影 星元组的引用,m是对 电影元组的引用。WHERE 子句在询 问我们能否确定 通过引用 s 所引用的 MovieStar 元 组的name分量就是Mel Gibson。如果是这样, 那么我们就将获得通过m所引用的Movie元组的title分量的值, 而且该值是该查询产生的元组之一。8. 5. 6 引用的 作用域为了回答例 8. 27 中的那种查询, SQL3 数据库系统必须把像 star- > name 这样的利用引用的表达式解释为对特定关系的 name 域的引用。一个简单的方法是查看 StarsIn 的每个元组, 并利用它的st ar引 用查 看所 引用 的 元组 是否 具有 姓名“Mel Gibson”。然而,·023·
间接引用和提取分量SQL3 和OQL之间的差别之一可以在对- > 和 点运算符的解释 上看出来。回忆一下 8. 2. 3 节, 在 OQL 中, 点和- > 运算符含义相同。每个都应用于 OQL 元组对象,而返回该对象的分量。在SQL3 中和在C中一样, 这些运算符是有区别的。我们只能把- > 用于对元组的引 用, 并且只 能够把点运算符 ( SQL3 中写成 两个点) 用于元组变量本身。和在C中一样, 如果r是对元组t的引用, 那么r- >a产生和t. .a相同的值。如果StarsIn很大, 那么用这种方法回答该问题将很费时间。如果DBMS允许 我们在某个关系R的name属性上 建立索引的话, 就可能有较 好的方法把 我们从一个特定 的值( 例如,“Mel Gibson”) 引到某 些 StarsIn 元 组, 而 这些元组 引用name等于“Mel Gibson”的关系R的 元组。但 是我 们利 用这样 的索 引在哪 个( 或者 哪些) 关系 R 中进行查找呢? 在这个例子中, 我们知道任何 StarsIn 元组的 star 属性值 都是对某个元组的引用, 而该元组必须在类型是 StarT ype 的关系中。由于只给出这样一 个关系,MovieStar, 所以我们希望它是该引用所引用的关系。然而, 有 可能把其他关系也 说明为类型St arT ype, 如 果这样, 就 需要在每个关系 的索引中查找姓名“Mel Gibson”。如果由于模式设计者已知的原因, 所有的引用都针对一个特定关系的元组( 通常是这种情况 ) , 那么这种查询 可能会浪费时间。 于是,SQL3 提供 了一种机制来指定引用属性引用的是哪个关系。我们对其属性类型为引用的关系进行说明时可以增加一个子句, 表示为:SCOPE FOR〈属性〉IS〈关系〉该语句的意思是所命名的属性( 必须是引用类型) 总是引用所命名的关系的元组。例 8. 28 为了保证在表 StarsIn 中, Star 的引用总是对 MovieStar 元 组的引用, 同时movie的引 用总是对Movie元组的 引用, 我们可 以写出如 图 8. 14 所示 的关系St arsIn的说明。 □ CREATE ROW TYPE StarsInType(star aREF (StarT ype) ,movie REF(MovieT ype)) ;CREATE TABLE StarsIn OF TYPE StarsInTypeSCOPE FOR star IS MovieStar,SCOPE FOR movie IS Movie;图 8. 14 说明引用属性的作用域8. 5. 7 作为值 的对象标 识面向 对象语 言通 常遵循 的原 则是, 对象ID是内 部系统 值, 不能 通过查 询 语言 访问。例如OQL就做了这样的假 定。然而, 我 们不能显式地引 用对象ID在原则上是没有 理由·123·
的, SQL3 给了我们这种能力。在关系或者它的行类型说明中, 我们可以有一个属性, 它的值是对同类型元组的引用。如果我们在行类型或者表的说明中增加如下子句:VALUES F OR〈属性〉ARE SYST EM GENERAT ED那么命名属性的值将成为对该引用所在的同一元组的引用。于是, 这样的属性既可以作为该关系的主键码, 也可以作为它的元组的对象 ID。例 8. 29 让我们重写图 8. 13 从而使MovieStar和Movie都具有对象ID属性, 分别称为 star - id 和 movie- id。更改后的模式如图 8. 15 所示。该图和图 8. 13 之间的差别在于:1. 把movie-id属性加到行类型MovieT ype中。2. 把 star- id 属性加到行类型 StarT ype 中。3. 把表Movie的movie-id值由系统产生这一语句加到该表的说明中。4. 把表 MovieSt ar 的 star- id 值由系统产生这一语句加到该表的说明中。5. 保留例 8. 28 中的SCOPE说明。 ?CREATE ROW TYPE MovieType(movie- id REF(MovieType) ,title CHAR( 30) ,year INT EGER,inColor BIT( 1)) ;CREATE ROW TYPE AddressType(str eet 6CHAR( 50) ,city CHAR( 20)) ;CREATE ROW TYPE StarType(star- id nREF(StarType) ,name CHAR( 30) ,address AddressType) ;CRE ATE ROW TYPE StarsInType(star JREF(StarType) ,movie REF(MovieType)) ;CREATE TABLE Movie OF TYPE MovieT ypeVALU ES FOR movie- id ARE SYSTEM GENERATED;CREATE TABLE MovieStar OF TYPE StarTypeVALU ES FOR star-id ARE SYSTEM GENERATED;CREATE TABLE StarsIn OF T YPE StarsInTypeSCOPE FOR star IS MovieStar,SCOPE FOR movie IS Movie;图 8. 15 把对象 ID 加到关系中现在我们有一种更方便的方法来写例 8. 27 中讨论的查询, 以找出Mel Gibson主演的·223·
电影。在 WHERE 子句中我们可以使 StarsIn 中的两个引用等于关系 MovieStar 和 Movie的两个对象ID属性, 而这两个对象ID属性是对它们自己的元组的自引用。该查询如下: SELECT Movie. title FROM StarsIn,MovieSt ar,Movie WHERE St arsIn. star = MovieStar. star- id ANDSt arsIn.movie=Movie.movie-id ANDMovieStar. name = ‘Mel Gibson' ;也就是说,FROM子句告诉我们, 要考虑的三部分分别由关系StarsIn、MovieSt ar和Movie 的元组组成。WHERE 子句的第一个条件是, 来自 StarsIn 的元组一定引用( 用它的star分量) 来 自MovieStar的元组。类似地,WHERE子句的 第二个 条件表明,St arsIn元组 引 用 ( 用 它 的 movie 分 量 ) 来 自 表 Movie 的 元 组。这 两 个 条 件 的 作 用 是, 要 求 来 自MovieSt ar和Movie的元组代表StarsIn元组中成对的影星和电影。然后, WHERE 子句的第三个 条件要求 所考虑的 影星是 Mel Gibson, 而 SELECT 子句将产生所考虑的电影名。注意, 我们仍然可以像例 8. 27 那样利用引用来写该查询。实际上, 那种方法比该查询 更简单, 不过写该查 询就像我 们做过的 那样, 用来说 明使用对 象ID属性所特有的某些可能情况。 □8. 5. 8 本节练 习练习 8. 5. 1: 为下列类型写行类型说明:(a) SNameT ype, 具有 分量 教名 (first name) , 名(middle name) , 姓(last name) 和学位( title) 。* (b) NPersonT ype, 具有某个人的姓名以及对其父母的引用。必须使用(a) 中说 明的行类型。(c)MarriageT ype, 具有结婚日期以及对丈夫和妻子的引用。练习 8. 5. 2: 用 行类型说明和适 当的引用属性来 重新设计练习 4. 1. 1 中不断滚动的 产品数 据 库 模 式。特 别 是, 让 关 系PC,Laptop和Printer的model属 性 成 为 对 该 型 号 的Product 元组的引用。练习 8. 5. 3: 利用练习 8. 5. 2 中的模式写下列查询。在合适的时候尽量使用这些引用。( a) 找出其 PC 硬盘容量大于 2G 字节的 PC 制造商。(b) 找出激光打印机的制造商。! ( c) (产生一个表, 表中给出每个 型号的 便携式电 脑, 而 该型号 便携式电 脑的处理 器速度在同一制造商制造的所有便携式电脑中是最高的。! 练习 8. 5. 4: 我们在 练习 8. 5. 2 中建 议: 表 PC, Laptop 和 P rinter 中的型号 可以是对 表Product的元组的引用。Product中的model属性也能成为对该类型产品的关系元组的引用吗? 为什么能, 或者为什么不能?* 练习 8. 5. 5: 用行类型说明和 适当的引用属性 来重新设计练习 4. 1. 3 中不断滚动 的战列舰数据库模式。练习 8. 1. 2 中的模式提示了这些引用属性可以用在哪里。查找多对一联系, 试着用具有引用类型的属性来表示它们。·323·
练习 8. 5. 6: 利用练习 8. 5. 5 中的模式写出下列查询。在合适的时候尽量使用引用而避免使用连接( 也就是,FROM子句中避免使用子查询或者多个元组变量) 。* (a) 找出排水量在 35 000 吨以上的舰艇。 ( b) 找出至少有一艘舰艇沉没的战役。! (c) 找出有 1930 年以后下水的舰艇等级。! ! (d) 找出至少有一艘美国舰艇受到损坏的战役。8. 6 SQL3 的抽象数据类型SQL3 的行类型和对行类型的引用提供了OQL对象的许多功能。另外它们还允许我们使用SQL运算符( 如插入和删除) 来方便地修改“对象”。比较起来,OQL倾向于在外层的面向对象编程语言( 如 C++ ) 中进行修改。然而, 行类型不提供面向对象编程语言的有效封装。回忆一下 1. 3. 1 节, 我们“封装”了一个类来保证对象只能通过该类所定义的固定运算的集合来修改。封装的目的是防止在数据库的设计者不希望或未想到的一些情况下使用数据时通常会发生的编程错误。行 类 型 没 有 封 装, 我 们 可 以 使 用 SQL3 能 表 示 的 任 何 操 作 来 操 作 行 类 型 的 元 组。ODL接口( 类) 没有完全封装, 所以我们可以用OQL查询的形式来访问对象的分量。另一方面, 查询对象的内部 结构通常比以无 计划的方式修改 对象危险性小一 些。在OQL中,除了通过方法就不能更新对象( 见 8. 1. 2 节) 。可能用外层的传统语言( 如C++ ) 编写的这些方法, 只能用于该类的对象。SQL3 中 还 有 一 种 的 确 支 持 封 装 的“ 类 ”的 定 义: 抽 象 数 据 类 型 (Abstract DataT ype) , 即ADT。ADT对象可以用作元组的分量, 而不是作为元组本身。然而, 典型的对象本身都具有元组的结构, 就像 ODL 对象通常具有带分量的结构一样。8. 6. 1 ADT 的定 义ADT 定义的格式如图 8. 16 所示。1) 行是建立语句, 引入 ADT 名字。2) 行表示属性的名字及其类型的列表, 中间用逗号分开。 1) CR E ATE TYPE〈类型名〉(2) 属性及其类型的列表3) 该类型的 = 和 < 函数的可选说明4) 该类型的函数( 方法) 的说明5) ) ;图 8. 16 定义抽象数据类型图 8. 16 的 3) 行表示了比较运算符 = 和 < 的可选说明。相等函数的说明格式是EQUALS〈实现相等函数的名字〉< 函 数的定 义类 似, 只不过 用关 键字LESS THAN代 替EQUALS。①注意, 其他 四种 比·423·①随 着SQL3 标 准的 发展, 这些 函数 可能将 不用 专门处 理, 但是 还将不 得不 像这 种类 型的 任 何其 他函 数一 样进行定义 和使 用
模式和数据ODL是规定数据库模式即数据库结构的语言。它 不具有定义数据 库中实际内容的功能, 也不提供对数据的查询或操作。正如我们在 1. 1 节提到的, 像 ODL 这样规定模式的语言通常称为数据定义语言, 而规定数据库的内容或者查询和修改其数据的语言则称为数据操纵语言。只有在第 4 章从用户的角度去看待数据库时, 我们才讨论数据操纵语言。数据定义语言是从设计者的角度观察数据库的核心。式面向对象计算的标准。ODL的主要用途是书写面向对象数据库的设计, 进而将其直接 转换成对面向对 象数据库管理系统( OODBMS) 的说明。因为 OODBMS 总是把 C+ + 或 Smalltalk 作为它的基本 语言, 因此 必须 把ODL转 换成 其中 一种 语言 的说 明。ODL和 这两 种语 言相 似( 但 和C+ + 更为相似 ) , 所以图 2. 2 所 提出的转换是非 常直接的。相比之 下, 要将 ODL 或 实体联系设计转换为适合更常见的关系数据库管理系统(RDBMS) 的说明却相当复杂。图 2. 2 把ODL设计转换成对OODBMS的说明2. 1. 1 面向对 象的设计在面向对象的设计中, 人们把准备模型化的世界看作由对象组成, 而对象是某种可观察的实体。例如, 人、银行的帐户、航班、大学的课程、建筑物等都可以作为对象。假定对象有 唯一 的对象 标识 (OID,Object IDent ity) , 这是 它和 任 何其 他对 象的 区别, 就像 我们 在1. 3. 1节讨论的那样。为了组织信息, 我们总是将具有相似特性的对象归为一类。在数据库中, 对象和类的概念与面向对象编程语言 ( 例如C+ + 或Smalltalk) 中得到 的那些概念基本 相同( 再 回忆一下我们在 1. 3. 1 节对面向对象概念的讨论) 。然而, 当提到 ODL 面向对象设计时, 我们应该从两个不同的方面考虑一类对象的相似特性。· 由属于一类的对象所表示的现实世界在概念上 应该是类似的。比 如, 将银行 的所有顾客归为一类和将银行的所有帐户归为另一类都是有意义的。但是将顾客和帐户合并 成一类是 没有意 义的, 因为在银 行世界 中它们很 少有或 者没 有共同 之处,而是扮演着本质上不同的角色。· 属于一类的对象其特性必须相同。当用面向对象 的语言编程时, 我们经常把 对象看成记录, 就像图 2. 3 所假设的那样。对象含有域或槽, 值将放在其中。这些值可能是普通类型, 如: 整型、字符串或数组, 也可能是对其他对象的引用。这些值还可能是方法, 也 就 是 用 于对 象 的 函 数。然而, 我们在学习ODL时, 不强调方法的使·91·
较运算符可以由此构成, 不需要显式定义。例如, ≤ 是“ = 或者 < ”, 而 ≥ 是“不 < ”。如果 定义了 = 和 < , 那么在WHERE子 句中就 可以 比较ADT的 值, 就像 传统 的SQL类型( 例如整数和字符串) 那样。4) 行给出了该 ADT 的其他函数( 也就是方法) 的说明。SQL3 对每个 ADT 提供了一些不需要说明或者定义的“内置”函数。它们包括:1. 构造函数(constructor function) , 返回该类型的新对象。该对象的所有属性都初始化为 NULL, 如果 T 是 ADT 的名字, 那么 T ( ) 就是构造函数。2. 观察 函数(observer function) , 对于 每个属性 都返回该 属性的 值。如果A是属 性名, 并且 X 是一个变量( 它的值是 ADT 对象) , 那么 A( X) 就是对象 X 中属性 A 的值。我们也可以使用更加传统的表示法 X. A 来表达同样的意思。3. 变异函数(mutator function) , 对于每个属性都设置该属性值为新值。它们通常用在赋值语句的左边, 其使用方式将在 8. 6. 2 节讨论。注意, 要实现封装, 就需要防止这些函数公用。SQL3 使用的方法是拥有函数的 EXECUT E 权限。这种权限可以像 7. 4. 1 节讨论的SQL2 的 6 种权限那样授予和取消。其他函数可以在 CREAT E T YPE 语句内部或者外部定义。即使它们是外部定义的,也可以只使用内部定义的函数, 包括上面所列的“内置”函数。例 8. 30 我们在例 8. 22 定义了由st reet和city分量组成的 地址为行类型。我 们可以用另一种方式将地址说明为同样结构的 ADT 。这种方法有封装地址的 作用; 如果 不把它们的观察和变异函数置成公用的, 就不能访问 street 和 city 分量。 u1) CREATE TYPE Ad\dressADT(2)street CHAR( 50) ,3) city CHAR( 20) ,4)EQU ALS addrEq,5) LESS T HAN addrLT 可在此说明的其他函数 ) ;图 8. 17 地址 ADT 的定义图 8. 17 给出了ADT addressADT的定义, 这里不包括与ADT相关的函数或者方法的实际定义。1) 行给出了ADT的名字。2) 和 3) 行 定义了表示方法, 一个元 组具有名为 street 和 city 的两个分量。这些 分量类型与例 8. 22 一样: 字符串的长度分别为 50 和 20。4) 和 5) 行 告诉 我们AddressADT的相 等函 数称 为addrEq, 而 < 比较 函 数则 称 为addrLT 。 由于 我们 没有 提供 它 们的 定义, 因 此还 不 知道 这些 函数 做 什 么。我 们将 在 例8. 32 给出我们选择的这些函数的定义。在那 里, 我 们 将按 字 典顺 序 对 地址 进 行 排序, 首先按照城市名, 然后按照街道名。 □下一个例子 说明如何将 ADT 的功能引入 SQL 程序数据类型 ( 这些数据 类型不是 数据库管理系统用于何处的传统假定可以预见的) 。现在在数据库中存储非常大的对象( 比如图像、音频剪辑或者电影) 都是可行的。然而, 我 们 在 这 些 对 象 上 执 行 的 操 作 不 像“标·523·
视频的MPEG编码因为视频需要特别大的空间来存储, 所以它通常用几种标准的压缩模式之一来进行编码。最常用的 MPEG( Motion Picture Experts Group, 运动图象专家组( 全球影像/ 声 音/ 系 统压 缩标准 ) ) 模式 利用 了这 样一个 事实: 动 画的 一帧 与它 前面 的 帧非 常相似。这样, 一帧的各个区域可以用前一帧类似区域的指针来表示。注意, 前一帧的区域可能在 同一位置( 如果它是静止背 景的一部分) , 也可能 在不同的 位置( 如果它 是运动物体的一部分) 。尽管MPEG压缩视频比标准的压缩文本 模式好得多, 但是用MP EG压缩一个小时的视频仍然需要 1G 字节。而且, 由于允许相应区域有少许的不同, 因此视频的质量通常稍微有些降低。为显示视频而进行的解压缩过程同样非常复杂。尽管有这些问题,MPEG 还是代表了图片质量、所用空间和所需要的计算能力之间良好的折衷。准”的 SQL 操作( 例如比较、打印、聚合等等) 。相反, 我们要显示这些对象通常需要使用复杂的解码算法来实现, 将来甚至可能做复杂的图像比较或识别图像的重要特性。例 8. 31 假定我们想要一部电影的MPEG编码(MPEG是视频压缩 标准格式, 详见主题框) 的ADT Mpeg。在技术上,MPEG编码是字符串, 所以可能认为VARCHAR类型比较合适。然而, 通常 MP EG 编码视频的长度很大( G 字节) , 把视频作为字符串来处理是不现实的。为了支持视 频和其他非常大 的数据项, 现代数据库系统 支持一种叫做BLOB(BinaryLarge OBject, 大容量二进制 对象) 的 特殊数据类型, 一种可能很 长的特殊类型的 位串, 如果需 要甚至可 以达到 G 字 节。在下例中 我们将假 定 BLOB 类 型是数据 库系统的 内部 类型。于是ADT Mpeg的合适定义如图 8. 18 所示。 V1) CREATE T YPE MpMeg(2)video BLOB,3) length INTEGER,4) copyright VARCHAR( 255) ,5)EQUALS DEFAU LT,6)LESS THOAN NONE这里放函数的定义 ) ;图 8. 18 MPEG ADT 的定义2) 行定义BLOB类型的video属 性。该属 性拥 有非常 大的MPEG编码视 频。3) 和4) 行是另外两个“普通”的属性: 视频的长度( 运行时间) 和版权说明。5) 行说明类型Mpeg值的相等比较是默认的: 全等。也就是说, 当且仅当两个 Mpeg 对象 在相应属性上逐 位相同时这两个对象才是相等的。我们可以设想为“相等”写一个更复杂的定义, 以反映这样的想法: 如果解码并且显示在合适分辨率的屏幕上的两个Mpeg对象看起来 相同, 就认 为两个Mpeg值“相等”, 但是在这里我们将不做这件事。6) 行说明Mpeg值之间没有 < 的定·623·
大容量二进制对象对用户来说,BLOB看起来像巨大的位串, 但是在现象背后, 它们的 实现比长度限制为像 255 字节这样小的字符串来说要远为复杂。例如, 作为元组的分量来存储巨大的字符串是没有意义的, 所以必须由所处的文件系统把它们分开存储。对于 另一个 例子, 7. 3. 4 节中讨 论的 客户 程序-服务程 序模 型假 定值和 元组 都容量适中, 而且服务程序将回答查询的整个元组集返回给客户。而立即把整个BLOB传给客户是没有意义的。例如, 如果客户向服务程序查找一个视频的片段, 服务程序应该在一段时间只传送过来一小段, 或许是几秒钟的有效视频信息。这样, 客户就可以开始放映这部电影, 而不 必在本地存储几 G 字节的 视频, 也 不必一直等到把 整个视频都接收完才开始播放。义。也就是, 如果A和B是类型Mpeg的值, 那么A<B的写法是非法的。 □8. 6. 2 ADT方法的定义在ADT的属性表之后, 可以增加任何函数说明的列表。函数说明的格式如下:FUNCTION〈名字〉(〈自变量〉)RET URNS〈类型〉;每个自变量都由变量名及其变量类型组成。自变量之间用逗号分开。函数有两种类型: 内部的和外部的。外部函数用宿主语言编写, 只有它们的署名出现在ADT的定义中。我们将在 8. 6. 3 节讨论外部函数。内部函数用扩充的SQL编写。下面是一些选项, 其中包括对 SQL2 以及对 SQL3 的查询语言部分的扩充。·∶ = 用来作为赋值运算符。· 要 说 明 函数 的 局部 变 量可 以 给 出 它的 名 字, 并 在 前 面 加 上冒 号, 后面 加 上 它 的类型。· 点运算符用来访问结构的分量。· 可以在 WHERE 子句中表达布尔值。· 可以用BEGIN和END把若干语句聚集到函数体中。例 8. 32 让我们继续研究例 8. 30, 在该例中, 我们定义了一个地址 ADT 。图 8. 19 给出了一些函数, 我们可以把这些函数和图 8. 17 中的类型建立语句合在一起。1) 到 6) 行为ADT AddressADT定义了构造函数。回忆一下,SQL3 提供了 0 自变量的内部构造函数, 名为 AddressADT ( ADT 本身的名字) 。然而, 我们希望有另一个构造函数, 它以stree和city的值为参数。我们 可以在需要的时 候调用它, 而使用同 样的名字 作为类是合法的和适宜的。在 1) 行, 我 们 看到 新的 构造 函数 说 明。它有 两 个自 变量 s 和 c, 分 别 表示 street 和city。它们的类型分别是长度为 50 和 20 的字符串。函数返回类型为AddressADT的值。2) 行说明 a 为 AddressADT 类型的局部变量。3) 到 6) 行是函数体。在 3) 行我们用内部构造函数 AddressADT ( ) 来 建立新的对象,并同时使它成为变量∶a的值。注意, 不能把内部构造函数和我们写的函数相混淆, 因为·723·
两个函数的自变量不同。也就是, 3) 行不能误解为递归调用。4) 行把第一个自变量复制到a的st reet分量中, 而 5) 行把第二个自变量复制到a的city分量中。最后, 6) 行返回 构造值 a。7 ) 和 8) 行 定 义 了ADT AddressADT的 相 等 函 数。回 忆 一 下 图 8. 17 的 4) 行, 把AddressADT 的相等函数说明为 addrEq, 所以我们必须使用该名字。该函数很简单, 当且仅当两个值的 st reet 和 city 分量都匹配时才返回 TRUE。该函数实际上是默认相等——值相同—— 也可能已经像例 8. 31 那样默认了。 (1) FU NCTION AddressADT(∶s CHAR( 50) , ∶c CHAR( 20) )RET URNS AddressADT;2) ∶a AddressADT;BEGIN3) ∶a∶ =AddressADT( ) ;4) ∶ a. str eet∶ =∶ s;5) ∶a.city∶ =∶c;6) RET URN∶ a;END;7) FU NCTION addrEq(∶a1AddressADT,∶a2AddressADT)RET URNS BOOLEAN;8) RETURN (∶ a1. street =∶ a2. str eet AND∶a1.city=∶a2.city) ;9) FU NCTION addrLT (∶ a1 Addr essADT,∶ a2 Addr essADT)RET URNS BOOLEAN;10)RETURN(∶a1.city<∶a2.city OR∶ a1. city =∶ a2. city AND∶ a1. street<∶ a2. street ) ;11) FUNCTION fullAddr(∶ a Addr essADT) RETUR NS CHAR ( 82) ;12) ∶z CHAR( 10) ;BEGIN13) ∶z∶ =findZip(∶a.street,∶a.city) ;14) RET URN(∶ a. str eet ©¦©¦' ' ©¦©¦∶ a. city ©¦©¦' ' ©¦©¦∶ z) ;END;图 8. 19 地址ADT的某些函数9) 和 10) 行是 < 函数,addrLT。在这 里, 如果 第一个城市按字典 顺序( 按 字母顺序)比第二个城市排在前面, 我们就可以说第一个地址在第二个之前。如果城市相同, 我们就比较街道的名字。11) 到 14) 行定义了函数fullAddr, 它处理类型为AddressADT的对象并返回整 个地址, 也就是街道地址、城市和 9 位数的( 加上连字符) 邮政编码。12) 行说明局部变量∶ z 临时保存邮政编码。在 13) 行调用函数findZip。该函数是外部定义的, 并有两个字符串自变量分别表示街道地址和城市。我们将在 8. 6. 3 节讨论外部函数说明的格式。通 过 某 种 复 杂 的 处 理, 可 能 在 另 一 个 数 据 库 或 者 复 杂 的 判 定 序 列 中 进 行 查 找,findZip将返回该街道和城市的正确邮政编码。在这里我们将不试图写出findZip。最后, 在·823·
14) 行, 我们把从对象∶ a 中得到的街道和城市与保存在∶ z 中的邮政编码连接起来。我们在该地址的三个分量之间加入单个空格隔开它们。 □8. 6. 3 外部函 数ADT 可能还有用某种宿主语言而不是用 SQL3 编写的方法。但只有函数的署名出现在ADT定义中, 同 时指出编写函数 所用的语言, 我们才能使用 这种函数。外部说明 的格式如下: DECLARE EXT ERNAL〈函数名〉〈署名〉 LANGUAGE〈语言名〉例 8. 33 为 了使 用例 8. 32 中 的外 部 函数findZip, 我 们需 要在ADT AddressADT的定义中说明它。因为该函数有两个自变量, 分别是长度为 50 和 20 的字符串, 并且返回长度为 10 的字符串, 所以合适的说明为: DECLARE EXT ERNAL findZip CHAR( 50)CHAR( 20)RET URNS CHAR( 10) LANGUAGE C;把语言说明为C意味着, 应把地址自变量以适合于C语言程序的格式传给findZip。8. 6. 4 本节练 习* 练习 8. 6. 1: 定义“P C”抽象数据 类型, 其对象 表示个人 计算机, 包括 处理器速 度、内 存容量、硬盘容量、CD的速度和价格。练习 8. 6. 2: 使用由内部函数扩展的 SQL, 为练习 8. 6. 1 中你定义的 ADT 编写下列函数:* ( a) J名为 newPC 的构造函 数, 它具有 PC ADT 的 5 个属 性值, 并且返 回该类型 的新对象。回忆一下, 你可以( 必须) 用内部构造函数PC( ) 来定义该函数。* ( b) S函 数 value, 用 PC 对象 作为自 变量并返 回对 PC 的“评价″, 它是表 明该 PC 如何“好”的实数。求PC“值”的公式是处理器速度加上 5 倍的RAM( 用M字节表示) , 再加上 50 倍的硬盘( 用 G 字节表示) , 和 10 倍的 CD 速度。(c) J函数bett er, 用PC对象作为自变 量, 并返 回另一PC对象, 后者具 有两倍的 处理 器速度、内存容量、硬盘和 CD 速度, 并有同样的 价格。你可以利 用( a) 中 的构造函数newPC。( d) M作为 PC 对象相等的函数 equalP C。如果两台 PC 具有同样的速度和硬盘容量,该函数就报告它们是“相等的”而不管其他分量的值。( e) @作为 ADT PC 的小于函数的函数 ltPC。如果 PC p 1 的“值”小于 P C p2 , 那么该函 数就 认为p1 <p2 , 其中“值 ”就 像在(b) 中定义 的那样, 你 可以使 用(b) 中 定义的函数 value。练习 8. 6. 3: 为舰艇定义抽象数据类型Ship, 包括舰艇 的名字、下 水日期、火炮的数目、火炮的口径、排水量、该舰艇在战斗中的一段 MPEG 编码视频片段, 以及关于该舰艇历 史的附录文献。·923·
练习 8. 6. 4: 为练习 8. 6. 3 中的 Ship ADT 写出下列函数的说明和定义。(a) 函数firePower, 用Ship对象作为自变量, 返回“火力”, 它是火炮数目乘以口径的立方。(b) 函数playVideo, 用Ship对象 作为自 变量, 使 用外部 定义 的播 放MPEG文件 的playMpeg 函数( 你必须说明它) 放映该舰艇的视频。(c) 构造函数newShip, 它接受 作为名字的值( 而没有其 他分量) , 并返回 具有该名 字的新 Ship 对象。(d) 作为Ship对象相等的函数equalShips。如果两艘舰艇名字相同并在同一年下水,那么该函数就报告这两艘舰艇相等, 而不管其他分量的值。(e) 作为ADT Ship的小于函数的ltShip函数。如果舰艇s1 的名字按字母顺序 比舰艇 s2 的名字排 在前面, 或者 如果名字 相同但 是 s1 比 s2 下 水早, 那 么该函 数就 认为s1 <s2 。8. 7 ODL/ OQL 和 SQL3 方法的比较在 列 举 了 为 面 向 对 象 的 数 据 库 管 理 而 提 出 的 两 种 主 要 的 标 准——ODL/OQL和SQL3 之后, 我们应该 看到这两种方法 在许多方面是不 同的。相似之处多 于不同之处, 这也是事实, 而且即使这两 种方法源于非 常不同 的模型 —— 面 向对象 的编程语 言和关系 数据库语言—— 但是它们都有效地采用了另一种核心模型的许多基础部分。在本节, 我们将列出这 两种方法的 原则区别 以及对 于折衷范 围选择的 不同之 点。同时, 我们将指出 SQL3 行类型 和 ADT 彼此之 间以及和 ODL 的接口 ( 类) 之间的区 别。这样, 事实上, 我们比较的是面向对象的三种不同方法:ODL/OQL、SQL3 行类型和SQL值类型。1. 编程环境。OQL假定它的语句将嵌入到编程语言中, 二者共享同样的编程和数据模型。假定这种语言是面向对象的; 例如, C++ , SmallT alk 或者 Java。另一方面, SQL3 假定它的对象不是外层的宿主语言的对象。在所有的SQL标准和实现中, 都有一个限 定的接口允许在 SQL 存储的数据和宿主语言变量之间传递值。在 SQL3 ADT 中使用外 部函数是 附加 的 通信 机制, 它 补充 了通 常 的SQL/ 宿 主语 言接 口, 就像 我 们 在 7. 1 节看 到 的那样。2. 关系的作用。对于SQL3 的数据视图来说, 关系仍然处于中心位置。实际上, 行类型 描述 关系, 而 ADT 描述 属性 的 新类 型。另 一方 面, 由于 对 象或 者 结 构的 集 合和 包 在select-from-where语句中的作用, 它们对于OQL来说是基本的。ODL/OQL的结构聚集与 SQL3 的关系非常类似。3. 封装性。行类型是没有封装的。可以用SQL允许的所有方 式对给定行类型 的关系、元组和分量进行查询和更新。SQL3 抽象数据类型按通常的意义封装。在封装的方法上,ODL的类非常类似于SQL3 的ADT。4. 类的范围。OQL 假定每个类维护着一个单独的范围。引用( 也就是 OQL 术语中的联系) 总是访问该范围中的某个或者某些成员。在SQL3 中, 我们可以维护一个行类 型的·033·
范围, 也就是, 包含该类型的每个已有元组的关系, 但是我们没有义务这样做。如果我们没有行类型的范围, 那么查找由给定引用所引用的元组所在的关系时就可能出现问题, 就像8. 5. 6 节中关于引用的作用域所讨论的那样。5. 对象的可变性。对象一旦建立了就是不可变的, 它的值没有一部分可以改变。像整数和字符串这种基本类型的对象在该意义上是不可变的。如果对象的分量可以改变, 而对象保持它的对象标识不 变, 那么该 对象就称为是可 变的。尽管在ODL/OQL中假定 对象更新是发生在其外层的宿主语言中, 而不是通过 OQL, 但是 ODL 类和 SQL3 行类型 都定义了可变对象的类。SQL ADT对象并不是完全不可变的。然而, 把它们的值用变异函数处理将产生新的值, 它可以代替旧的值, 这很像整数值属性上的 SQL UPDAT E 语句产生新整数, 它可以代替该元组中的旧整数。6. 对象标识。ODL 和 SQL3 ADT 都遵循对对象标 识的传统解释: 它是 系统产生 的量, 用户不能存储或操作它。然而,SQL3 行类型的引用不遵循该原则。用户可以在关系中建立一列, 而元组的对象标识就存放在该元组本身的这一列中, 好像它就是普通的值。结果元组的对象标识可以作为关系的键码。尽管它确实由于修改或者删除把悬挂的引用带到了数据库, 但是这种能力是有一定意义的。由于不允许对象标识成为属性, 典型的关系都具有两个键码: 对象标识和像社会保险号或者“证书号”( 我们用在不断滚动的电影实例中) 这样的替代值。8. 8 本 章 总 结 ODL 中的 方法: 除了在 第 2 章学 过的属 性和联 系之外, ODL 允许 我们说 明方 法作为接口规定的一部分。我们只定义方法的署名, 也就是输入和输出参数的类型。方法本身在外层的程序中定义并用面向对象的宿主语言来编写。 OQL类型系统:OQL中的类型是由类名和原子类型( 例如, 整数) 构成的。类型构造 符 Struct 用 来构 造结 构 以及 集合、包、列 表和 数 组的 聚集 类型。这 样, 除 了 在OQL中类型构造符的嵌套深度没有限制以外, 其类型系统与ODL一样。 OQL 中 的 select-fr om-where 语 句: OQL 提 供 了 与 SQL 类 似 的 select-from-where表达式。在FROM子句中, 我们可以说明 覆盖所有聚集的 变量, 包括 类的范围( 类似于关系) 和对象属性值的聚集。 OQL的 通 用 运 算 符:OQL提 供 了 实 质 上 与SQL类 似 的 全 称 (for-all) 、存 在( exists) 、IN、并 ( union) 、交 ( intersection) 、差 ( difference) 和 聚合 ( aggregation)运算符。然而, 聚合总是在聚集上进行的, 而不是在关系的一列上进行的。 OQL 的 分组: OQL 也在 select-from-where 语句 中提 供 GROUP BY 子句, 这 和SQL相类似。然而, 在OQL中, 每个组的对象聚集可以通过称为part ition的域名来显式地访问。 从OQL聚 集中提 取元 素: 我们可 以用ELEMENT运 算符来 获得 单个元 素聚 集中的单个成员。我们可以用以下方法来访问具有 多个成员的聚集 的元素: 首 先用select-from-where语句中的ORDER BY子句 把聚集转 换成列 表, 然 后用外层 的·133·
宿主语言程序的循环按顺序访问该列表的每个元素。 SQL3 中的对象:SQL3 提供了两种类型的对象: 行类型和抽象数据类型。行类型是元组的类型, 而抽象数据类型则是元组分量的类型。 行类型的对 象标识: 每个行类型都有一 个引用类型, 而且该引用类 型的值是元 组的对象 ID。SQL3 允许属性的类型为对它所在关 系的行类型的引 用, 而且该 属性的值为它所在元组的对象ID, 这样就允许对象ID也作为所在关系的键码属性。 SQL3 中的抽象 数据 类型: 在 SQL3 中 可以 用 CREAT E T YP E 语句 说明 ADT 。ADT的值是具有一个或者多个分量的记录结构, 可能还有相关的方法。 SQL3 ADT 的方法: 可以为 ADT 说明函数( 方法) 。它们可以用类似于 SQL 的编程语言编写, 也可以说明为用宿主语言编写的外部函数。8. 9 本章参考文献OQL的引用和ODL相同: [ 1] 。有关SQL3 的资料可以按照第 5 章文献注释中 的描述来获得。此外, [ 3] 是行类型的来源, [ 2] 是 SQL3 抽象数据类型的早期说明, 从那时起,该标准已在许多方面得到了发展。[ 1] GCattell,R.G.G. (ed. ) ,The Object Database Standard:ODMG-93Release1. 2,Morgan-Kaufmann, San Francisco, 1996.[ 2] 'Melton,J. ,J.Bauer,and K.Kulkarni,Object ADT' s(with impr ovements for value ADT' s) ,ISO WG3report X3H2-91-083,Apr il, 1991.[ 3] :Kulkarni, K. , M. Car ey, L. DeMichiel, N. Mattos, W. Hong, and M. Ubell, Introducingreference types and cleaning up SQL3' s object model,ISO WG3report X3H2-95-456,Nov. ,1995.·233·
OID的特性正如我们在 1. 3. 1 节提到的 那样, 面向对 象数据库 往往非常 大, 以至于所 需要的OID 的数量大大超过在一个地址空间中地址的数量。于是, 面向对象数据库系统通常用某种模式来 建立与每个对象 相联系的唯 一的字 符串; 这个字符 串往往 很长, 可能是十六个字节。例如一个对象可能把它建立的时间( 用足够小的单位来度量, 以至于在一台机器上 不能同时建立两 个对象) 和 建立它的主 机的标 识一起作 为它的标 识( 如果数据库系统分布在几台主机上) 。图 2. 3 表示帐户的对象 用, 因 为任 何 面向 对 象 的编 程 语 言对 方法的使用都是类似的。在 8. 1 节, 我们将回到对ODL方法问题的讨论上。虽然 将对 象 想象 成具 有记 录 的 结构 往 往是有益 的, 但是 本 章主 要讨 论抽 象层 次 上的 设 计。因此, 我们首先应强调类 及其特性的更抽 象的概念, 而 不考虑实现的细节, 例如记 录的域是如 何组织的, 或者 对象是 否确实用 记录结构 来表示。当说明 ODL 类的设计时, 需要描述的三种特性是:1. 属性 (att ribute) , 是一些 特性, 它 们的类型是由基 本的数 据类型( 例如 整型或字 符串) 构成的。特别是, 一个属性有一个和任何其他类无关的类型。在 ODL 中, 属性的类型是有限的; 我们将在 2. 1. 7 节进一步讨论。2. 联 系(relationship) , 是 一些特 性, 它 们的类型 或是对 某类对象 的引 用或是 这种 引用的聚集( 例如, 一个集合) 。3. 方法 (method) , 是能用于 该类对象 的函数。如 上所述, 在这 里我们不 强调方法 的使用。2. 1. 2 接口说 明在 ODL 中, 形式最简单的类的说明应包括:1. 关键字 interface( 接口) 。2. 接口的名字( 也就是类名) 。3. 用花括号括起来的类的特性表。回忆一下, 这些特性是属性、联系和方法。也就是说, 接口说明的简单形式是interface`〈名字〉{〈特性表〉}2. 1. 3 ODL中的属性最简单的一种特性称为属性。这些特性通过将一个对象和某个简单类型的值相连来·02·
描 述 该对 象 的 某 个 方面。 例 如, 对 每 个 人来 说, 对象“人 ”都 会 有 一 个 字 符 串 型 的 属 性name, 它的值是这个人的名字。对象“人”还会有一个属性birthdate, 它是一个由三个整数组成的元组( 即一个记录结构) , 代表这个人的出生年、月、日。例 2. 1 图 2. 4 是 ODL 中对电影类的说明。它不是完整的说明; 我们将在以后 对其进行补充。1) 行说明Movie是一个类。在ODL中使用关键字interface来说明类,①1) 行之后是对四个属性的说明, 这四个属性是所有的电影对象所共有的。 ?1) intberface Movie{2) attribute string title;3) attribute integer year;4)attribute integer length;5) battribute enum Film{color,blackAndWhite}filmT ype;};图 2. 4 ODL中类Movie的说明第一个属性在 2) 行上, 命名为 title。其类型为 string, 即长度 未知的字符串。我 们希望在任何 Movie 对象中, 属性 tit le 的值都是电影的名字。3) 、4) 行说明下面两个属性 year和lengt h都是整形, 分别代表电 影的制作年份和 以分钟计算的电影 的长度。5) 行是 另一个属性fileT ype, 它说明电影是彩色的还是黑白的。其类型为枚举类型。枚举类型的名字是 Film。枚举属性的值从文字表中选择, 在这个例子中就是从 color 和 blackAndWhite 中选择。符合 上述定 义的 类Movie的对象, 可 以认 为是具 有四 个分量 的记 录或 元组, 四个 属性中的每一个都对应一个分量。例如,( ″Gone With the Wind″, 1939, 231, color)就是一个Movie对象。 □例 2. 2 在例 2. 1 中, 所有的属性都属于原子类型。 我们也可以有类 型为结构、聚集或结构聚集的属性, 正如我们将在 2. 1. 7 节讨论的那样。以下是非原子类型的例子:我们可以如此定义类 Star:1)interface Star{2) attribut e string name;3) attribut e Sruct Addr{st ring street,string city}address; };2) 行说明一个字符串的属性 name( 影星的名字) 。3) 行说明另一个属性 address。这个属性属于记录结构类型。这个类型的名称为 Addr , 类型包括两个域 street 和 city。两个域都是字符串型。通常, 在ODL中可以通过使用关键字St ruct并用花括号将域名及其类型表括起来, 定义记录结构类型。 □·12·①从 技术上 讲, 在ODL中 , 类 是接 口以及 与该 接口有 关的 数据结 构和方 法的 实现。 本节 并不讨 论ODL接 口的实现, 但将 继续 把接口 说明 作为“类”的 定义。
2. 1. 4 ODL 中的联系虽然我们可以 通过研究属性获 得关于对 象的很多 信息, 但有时 有关一个 对象的关 键性信息是它与同类或不同类的其他对象连接的方式。例 2. 3 现在, 假定我们想在例 2. 1 的 Movie 类的说明中增加一个影星集合的特性。因为影 星本身是 一个 类, 正如例 2. 2 中所描 述的, 我 们不能 把这 个信 息作为Movie的 属性, 因为属 性的类 型不 能是 类也不 能从 类中构 造。相 反地, 电影 的影 星集合 是 Movie 和Star两类之间的一种联系。我们在Movie类的说明中, 用下面一行表示这种联系: relationship Set〈St ar〉stars;这一行可以出现在图 2. 4 的 1) 至 5) 行的任一行之后。它说明在Movie类的每个对象中, 都有一个对 Star 对象的引用集合。引用集称为 stars。关键字 relationship( 联系) 说明stars包括 对其他 对象的引 用, 而 在〈Star〉之前 的关键 字Set表明st ars引 用St ar对象 的集合, 而不是单一 的对象。一般说来, 如果一个 类型是某个其他 类型 T 作 为元素的集合,那么在ODL中, 用关键字Set和用尖括号括起来的类型T来定义它。实际上, 我们可以想象用指向 Star 对象的指针 列表来表示集合 stars; 而 Star 对 象实际上不会出现在Movie对象中。但是在数据库设计阶段, 实际的表示是未知的, 而联系的重要方面是从 Movie 对象可以方便地找到这部电影的影星。 □在例 2. 3 中, 我们 看到用 一个联系 把Star对 象的集 合stars和单一 的对象Movie联系起来。联系也可能把单一的对象和要说明的类的对象联系起来。例如, 假定在例 2. 3 中,已经给出联系的类型是Star, 而不是Set〈St ar〉, 如下行所示: relationship Star starOf;那么这个联系将把单一的Star对 象和每一部电影 联系起来。在这里, 这种方案 没有太大意义, 因为, 一部电影通常有多个影星。然而, 我们将看到在其他许多例子中, 单值联系是合适的。2. 1. 5 反向联 系正如我们可能 希望了解给定电 影的所有 影星一样, 我们 也可能 希望知道 给定影星 主演的所有电影。为了把这个信息放入Star对象中, 我们可以在例 2. 2 类St ar的说明中增加以下一行: relationship Set〈Movie〉st arredIn;然而, 这 一行 和 对 Movie 的类 似 说 明忽 略 了电 影 和影 星 之间 联 系 的一 个 很重 要 方面。 我 们 希 望 如 果 影 星S在 电 影M的 影 星 集st ars中, 那 么 电 影M就 在 影 星S的starredIn 集 合中。我们通过将关键 字 inverse 和另一个联系 的名字放在每个 联系的说 明中来表示stars和st arredIn两个联系之间的这种关联机制。如果另一个联系在其他 某个类中, 像通常的情况那样, 那么我们通过类名、随后的双冒号∷和联系名来引用这个联系。例 2. 4 为了把类St ar的联系starredIn定义 成类Movie的联系st ars的反 向联系,我们把类 Star 的说明改写成图 2. 5 所示的那样。在 4) 行, 我们不但看到了联系 starredIn的说明, 而且事实上还有一个反向联系Movie: :stars。因为联系stars是在另一类Movie·22·
对反向联系的要求作为抽象的设计语言,ODL要求有反向 联系。对这个要求 的解释是, 每当有一个路径从一个对象出发到它的相关对象时( 例如从电影对象出发到影星对象) , 相反方向上 的 路 径 ( 从 影 星 到 他 们 主 演 的 电 影) 也 是 可 能 的。也 就 是 说, 如 果 有 一 个 影 星Charltion Heston, 我们可以审查所有的电影对象并且检查它们的影星。然后我们可以列出由Heston主演的电影。ODL要求给这个反向的过程赋予一个联系名。然而, 当我们将 ODL 转换 成实际的 编程语 言( 例 如嵌入 式的 C+ + 语 言) 的说明时, 我们知道将引用只放入电影对象中, 而在影星对象中没有对电影的引用是可能的。于是, ODL 的嵌入式 C+ + 允许单向联系。因为我们在这里只关心设计, 而不是实现,我们将期望每个联系都有反向联系。中定义的, 因此在它之前有类名( Movie) 和双冒号∷。在引用不同类的特性时通常使用这种表示法。 □ 1) interjface Star{2)attribute string name;3)attribuute struct Addr{string street,string city}address;4)relationshipUSet<Movie>starredIninverse Movie: :stars; };图 2. 5 表明联系及其反向联系的类 Star在例 2. 4 中, 一对反 向联系各自都把 一个对象 ( 一部 电影或 一个影星 ) 和 一个对象 集相连。正如我们提到的, 有些联系是一个对象和另一类的单一对象相联系。但反向联系的概念并不改变。作为通用的规则, 如果类C的联系R把一个或多个对象y1 ,y2 , …,yn 的集合和类 C 的对象 x 相连, 那么 R 的反向联系就把 对象 x ( 可能还 有其他的对象) 和每个 yi相连。有时, 把从类 C 到某个类 D 的联系 R 形象化地按成对的对象或关系的元组列表表示出来, 会很有帮助。每一对都包括类 C 的一个对象 x 和类 D 的一个相关对象 y。例如:C Dx 1 ny1 kx 2 ny2 k… … 如果R的类型是Set〈D〉, 那么对于同一个C值可能有多个对。如果R的类型是〈D〉,那么对于一个给定的C值只能有一个对。于是,R的反向联系是具有相反分量的对的集合:·32·
联系类型之间的内涵我们应该意识到多对一联系是多对多联系的特例, 而一对一联系是多对一联系的特例。也就是说, 任何多对多联系的有用的特性也适用于多对一联系, 而多对一联系的有用的特性也适用于一对一联系。例如, 表示多对一联系的数据结构虽然对于多对多联系并不适用, 但是对于一对一联系是适用的。还要注意, 如果我们说一个联系R是“多对多”的, 实际上意味着联系R有多对多的自由度。随着 R 的改变, 有时它可能成为多对一的联系甚至一对一的联系。同样地,多对一的联系R有时可能成为一对一的联系。D Cy1 mx1 ly2 mx2 l… … 注意, 即使C和D同类, 这个 规则也成 立。有些联 系在逻 辑上连接 自己, 比如“childof”, 从“人”(“Persons”) 的类指向自己。2. 1. 6 联系的 多重性联系不论是把 一个给定的对象 和唯一的 相关对象 相连, 还是把 一个对象 和多个其 他对象相连, 都是有重要 意义的。在 ODL 中, 我们可以通过 在联系说 明中使用 或者不用 聚集运算 符( 例如 Set ) 来说 明这种选择。当我们 有一对相反的联 系时, 有 四种可能 的选择:联系在一个方向上是唯一的, 在两个方向上都是唯一的, 在两个方向上都不是唯一的。我们已经讨论过的Stars和Movie之间的 联系在两个方向上 都不是唯一的。也 就是说, 一部电影一般有多个影星, 一个影星也可以出现在几部电影中。下例详细描述了以前的例子, 在这里, 我们将介绍另一个类,St udio, 代表制作电影的制片公司。例 2. 5 在图 2. 6 中, 有三个类(Movie,St ar和Studio) 的说明。其中前两类已经在例2. 1 和 2. 2 中介绍了。St udio对象有属性name和address; 这些出现在 13) 行和 14) 行。注意: 地 址的 类型 在这 里 是字 符串, 而 不是 像 10) 行 的类 Star 的 address 属性 那 样使 用 结构。在不同的类中使用同名而不同类型的属性不是什么错误。在 7) 行, 我们看到一个从电影到制片公司的联系 ownedBy。因 为这个联系的类 型是Studio, 而不是 Set〈Studio〉, 因此我们是在说明每部电影都有唯一的所属制片公司。这个联系的反向联系出现在 15) 行。在那里我们看到了从制片公司到电影的联系owns。这个联 系的 类型是Set〈Movie〉, 表 示每个 制片 公司拥 有一 个电 影 集合 —— 可能 是 0, 可能 是1, 也可能是大量的电影。 □联系及其反向联系的唯一性要求称为联系的多重性。三种最常见的多重性是:1. 从类C到类D的多对多 联系是指, 在联系中, 每个C都和D的集合有关, 而在 反·42·
目 录第 1 章 数据库系统的世界 1……………………………………………………………………………1. 1 数据库系统的发展 1………………………………………………………………………………1. 1. 1 早期的数据库管理系统 1………………………………………………………………1. 1. 2 关系数据库系统 3………………………………………………………………………1. 1. 3 越来越小的系统 4………………………………………………………………………1. 1. 4 越来越大的系统 4………………………………………………………………………1. 2 数据库管理系统的结构 5…………………………………………………………………………1. 2. 1 DBMS 的组成概述 5……………………………………………………………………1. 2. 2 存储管理程序 7…………………………………………………………………………1. 2. 3 查询处理程序 7…………………………………………………………………………1. 2. 4 事务管理程序 8…………………………………………………………………………1. 2. 5 客户程序-服务程序体系结构 10………………………………………………………1. 3 未来的数据库系统 10……………………………………………………………………………1. 3. 1 类型、类和对象 10………………………………………………………………………1. 3. 2 约束和触发程序 13………………………………………………………………………1. 3. 3 多媒体数据 13……………………………………………………………………………1. 3. 4 数据集成 14………………………………………………………………………………1. 4 本书概要 15………………………………………………………………………………………1. 4. 1 设计 15……………………………………………………………………………………1. 4. 2 编程 15……………………………………………………………………………………1. 5 本章总结 16………………………………………………………………………………………1. 6 本章参考文献 17…………………………………………………………………………………第 2 章 数据库建模 18……………………………………………………………………………………2. 1 ODL 介绍 18………………………………………………………………………………………2. 1. 1 面向对象的设计 19………………………………………………………………………2. 1. 2 接口说明 20………………………………………………………………………………2. 1. 3 ODL 中的属性 20…………………………………………………………………………2. 1. 4 ODL中的联系 22…………………………………………………………………………2. 1. 5 反向联系 22………………………………………………………………………………2. 1. 6 联系的多重性 24…………………………………………………………………………2. 1. 7 ODL 中的类型 26…………………………………………………………………………2. 1. 8 本节练习 27………………………………………………………………………………2. 2 实体联系图 29……………………………………………………………………………………·Ⅲ·
向联系中, 每个 D 都和 C 的集合有关。例如, 在图 2. 6 中, stars 是从类 Movie 到类 Star 的多对多联系, 而st arredIn是从St ar到Movie的多对多 联系。在每个方向 上, 都允许 集合为空; 例如, 一部特定的电影可能没有知名的影星。2. 从类C到类D的多对一 联系是指, 在联系中, 每个C都和 唯一的D有关, 而在 反向联系中, 每个 D 都和 C 的集合有关。在图 2. 6 中, ownedBy 是从 Movie 到 Studio 的多对一的联系。我们说它的反向联系是从类 D 到类 C 的一对多的联系。例如在图 2. 6 中, 联系owns是从St udio到Movie的一对多的联系。3. 从类C到类D的一对一 联系是指, 在联系中, 每个C都和 唯一的D有关, 而在 反向联系中, 每个D都和唯一的C有关。例如, 假如我们在图 2. 6 中增加一个President类,它代表制片公司的总裁。我们希望每个制片公司只有一个总裁, 而没有人可以是多个制片公司的总裁。这样, 制片公司与其总裁之间的联系在两个方向上都是一对一的。 {1) interface Movie{2) attribute string title;3) attribute integer year;4) attribute integer length;5) attribute enum Film {color , blackAndWhite} filmType;6) relationship Set< Star > starsinverse Star : : starredIn;7) relationship Studio ownedbyinverse Studio : : owns; };8) interface Star{9) attribute string name;10)attribute Str uct Addr{string street,string city}address;11)relationshipSet<Movie>starr edIninverse Movie: :stars; };12) interface Studio{13)attribute string name;14)attribute string address;15)relationshipSet<Movie>ownsinverse Movie: :ownedBy; };图 2. 6 ODL 的类及其联系在我们使用“唯一”或“一个”之类的措辞谈论多对一或一对一时有一个微妙之处。在正常情况下要求这个“唯一”或“一个”的元素是实际存在的。例如, 对于每部电影都确实有一个制片公司, 而对于每个制片公司都确实有一个总裁。然而, 实际上, 所期望的唯一对象也可能并不存在。例如, 制片公司可能暂时没有总裁, 或者我们可能不知道哪一家制片公司拥有某一部电影。这样, 我们应该允许所期望的有关对象唯一值为空缺的。我们以后将看到在数据库中·52·
集合、包和列表为了理解集合、包和列表三者之间的不同, 要记住集合的元素是无序的, 并且每个元素只出现一次。包允许一个元素出现多次, 而元素及其出现是无序的。列表允许一个元素出现多次, 但是元素的出现是有序的。于是, {1, 2, 1}和{2, 1, 1}是相同的包, 而{1, 2, 1}和{2, 1, 1}是不同的列表。期望有真实值的地方经常出现“空”( Null) 值。例如, 在传统的编程术语中, 我们会发 现一个期望指向单一对象的指针实际上是一个空(nil) 指针。在 2. 5 节我们将讨论完整性约束的问题, 并了解保证预期的唯一对象必须毫无例外地存在的机制。2. 1. 7 ODL 中的类型ODL 给数据库设计者提供一个类型系统, 它和在 C 或其他传统的编程语言中的类型系统相似。类型系统是由单独定义的基本类型和某些递归规则构造的, 利用基本类型和某些递归规则由较简单的类型构成复杂的类型。在ODL中, 基本类型包括:1. 原子类型: 整型、浮点型、字符、字符串、布尔型和枚举型。最 后一种类型是一 个名字的 列表, 它和整型 是同义 词。我们在 图 2. 6 的 5) 行看到 一个枚 举的 例子, 在效 果上 就是, 把名字color和blackAndWhite定义成整数 0 和 1 的同义词。2. 接口类型, 例如 Movie 或者 Star , 实际上表示结构类型, 由属性和该接口的联系所对应的各种分量组成。这些名字代表用以下规则定义的复杂类型, 但是我们可以把它们看作是基本类型。这些基本类型可以通过下列的类型构造符组合成结构类型:1. 集合。如果T是任意类型, 那么Set〈T〉表示类型, 其值为所有元素的有限集, 而元素类型为 T 。使用集合类型构造符的例子出现在图 2. 6 的 6) , 11) , 15) 行。2. 包。如果T是任意类型, 那么Bag〈T〉表示类型, 它的值是类型为T的元素的包或多重集。包允许一个元素出现多次。例如, {1, 2, 1}是包而不是集合, 因为 1 出现了多次。3. 列表。如果 T 是任意类型, 那么 List〈T 〉表示类型, 它的值是类型为 T 的零个或多个元素的有限列表。作为一种特例,st ring( 字符串) 类型是List〈char〉类型的简化形式。4. 数组。如果 T 是一种类型而 i 是一个整数, 那么 Array〈T , i〉表示类型, 是由 i 个类型为T的元素组成的数组。例如,Array〈char, 10〉表示长度为十的字符串。5. 结构。如果T1 ,T2 , …,Tn 是类型,F1 ,F2 …,Fn 是域名, 那么Struct N{T1F1 ,T2F2 , …,TnFn } 表示其名 为N的类 型, 它的元 素是具有n个 域的结构。第i个域名是Fi, 类型 是Ti。例如, 图 2. 6 10) 行说明一个记录类型, 名字是Addr, 具有两个域。两个域都是字符串类型, 名字分别为st reet和city。前面四种类型—— 集合、包、列表和数组—— 统称为聚集(Collection) 类型。至于什么类型和属性有关, 以及什么类型和联系有关, 存在不同的规则。· 属性的类型首先由原子类型或者域是原子类型的结构组成。然后我们可以随意地·62·
把聚集类型应用于原始的原子类型或结构。· 联系的类型是接口类型或者是应用于接口类型的聚集类型。属性的类型不 能是接口类型, 而联系 的类型 不能是原 子类型, 记住 这些是很 重要的。正是这个差别区分了属性和联系。还要注意, 复杂类型构成属性和联系的方式有所不同。属性和联系都允许用任选的聚集类型作为最终的运算符, 但是只有属性允许结构类型。例 2. 6 一些可能的属性类型:( 1) integer( 整型) 。( 2)St ruct N{string field1,integer field2}。( 3) List〈real〉。( 4)Array〈Struct N{string field1,integer field2}〉。例 中( 1) 是原子类型; ( 2) 是由原子类型组 成的结构; ( 3) 是原子 类型的聚 集; ( 4) 是 由原子类型组成的结构的聚集。这是属性类型仅有的四种可能性。现在, 假如接口类型 Movie 和 Star 是可利用的基本类型。那么 我们可以构造联 系类型, 比如Movie或Bag〈Star〉。然而, 以下作为联系类型是不合法的:( 1) Struct N {Movie field1, Star field2}, 联系类型不能涉及结构。( 2)Set〈integer〉, 联系类型不能涉及原子类型。( 3) Set〈Array〈Star〉〉, 联系类型不能( 属性类型也不能) 涉及聚集类型的两个应用。2. 1. 8 本节练 习* 练习 2. 1. 1: 让我们为银行设计一个数据库, 包括顾客及其帐户的信息。顾客的信息包括他们的姓名、地址、电话和社会保险号。帐户包括编号、类型( 例如, 存款, 支票) 和结余。我们还需要记录拥有帐户的顾客。对这个数据库用ODL进行描述。为所有的属性和联系选择适当的类型。练习 2. 1. 2: 用下列方法修改你对练习 2. 1. 1 的设计。只描述变化部分, 不用写出完整的新模式。(a) 只能列出一个顾客作为某个帐户的拥有者。( b) 在( a) 的基础上, 一个顾客不能有多个帐户。(c) 地址分三部分: 街道(street) 、城市(city) 和州(st ate) 。并且, 顾客可以列出任意数目的地址和电话号码。(d) 顾客可以 有任意 数目的地 址, 由 (c) 中所示 的三元组 组成, 任一地 址都和 电话 集相连。也就是说, 我们需要记录每个顾客的地址, 而电话将连到每个地址。注意:对属性和联系的类型有不少限制, 应小心。练习 2. 1. 3: 给出按 ODL 设计的数据库, 记录球队、队员和球迷的信息, 包括:1. 对于每个球队, 球队的名字、队员、队长( 队员之一) 以及队服的颜色。2. 对于每个队员, 他/ 她的姓名。3. 对于每个球迷, 他/ 她的姓名, 喜爱的球队, 喜爱的队员以及喜爱的颜色。练习 2. 1. 4: 修 改练习 2. 1. 3, 为 每个队员记 录他所服 役的球队 的历史, 包括 在每个球 队的开始日期和结束日期( 如果他们转队) 。·72·
* ! 练习 2. 1. 5: 假如我们想要保存一个家谱。我们应该有一个类, Person。每个人所要记录的信息包括他们的姓名( 一个属性) 和下列的联系: 母亲、父亲和孩子。对类 Person 进行ODL设计。保证指明如:mother、father和children的反向联系仍是Person至Person的类。mother 的反向联系是联系 children 吗? 为什么是或为什么不是? 把每个联系及其反向联系作为一对集合来描述。! 练习 2. 1. 6: 让我们在练习 2. 1. 5 的设计中增加一个属性“教育”。这个属性的值是每个人 所 获 得 的 所 有 学 位 的 聚 集, 包 括 学 位 的 名 称 ( 例 如, 理 学 士 (B.S. ,Bachelor ofScience) ) 、学校和日期。结构的聚集可以是集合、包、列表或数组。分别描述采用这四种选择的结果。采用每一种方式会得到什么信息, 会失去什么信息? 丢失的信息在实际应用中是否重要?! 练习 2. 1. 7: 设计一个适合大学选课的数据库。这个数据库应当包括 学生、系、教授、课程、哪个学生选了哪 门课、哪 个教授教哪门 课、学 生的分数、课程 的助教 ( 由学 生兼任) 、一个系提供哪些课程以及你认为适当的任何其他信息。注意, 和以上练习相比, 该练习形式上更加自由, 对联系的多重性、适当的类型、甚至所要表示的信息都要由你做出决定。 ?1) interface Ship{2) attribute string name;3)attribute integer yearLaunched;4)relationship TG assignedT o inverse TG: :unitsOf; };5) interface T G{6) attribute real number;7) attribute string commander;8)relationship Se_t〈Ship〉unitsOfinver se Ship : : assignedTo; };图 2. 7 关于舰艇和特遣舰队的 ODL 描述练习 2. 1. 8: 图 2. 7 是类Ship和TG(T ask Group, 特遣舰队, 是舰艇的聚集) 的ODL定义。我们将对这个定义作一些修改。可以通过指出要修改的行, 并给出替代的内容, 或者通过在已编号的某一行之后插入一行或几行来对每个修改进行描述。描述下列修改:(a) 属性commander( 指挥官) 的类型变成两个字符串, 其中第一个是 军衔, 第二 个是姓名。(b) 一艘舰艇可以分配给多个特遣舰队。(c) 姊妹舰是按相同的计 划建造的相同的 舰艇。我们希望对每 艘舰艇都表示出 姊妹舰的集合( 除了它本身) 。你可以假定每艘舰艇的姊妹舰都是Ship的对象。* ! ! 练习 2. 1. 9: 在什么条件下, 一个联系就是它本身的反向联系? 提示: 把联系想 象成一个对的集合, 正如 2. 1. 5 节所讨论的那样。* ! 练习 2. 1. 10: 一个类型在 任何时 候都可以 同时是 合法的 ODL 的属性类 型和联系 类型吗? 解释为什么可以或为什么不可以。·82·
E/R联系的可视化用一个表也就是关系表示E/R联系, 用每一行表示参与联 系的实体对, 往往是有益的。例如, 联系Stars-in可视化为由如下各对组成的一张表:Movies( 电影) Stars( 影星)Basic Instinct Sharon StoneTotal Recall Arnold SchwarzeneggerTotal Recall Sharon Stone 当 然, 无 论 是 在 ODL 中, 还 是 在 E/ R 模 型 中, 都 没 有 特 定 方 法 使 联 系 一 定 能实现。这个表有时称为该联系所对应的联系集。表的各行就是联系集的成员。行可以表示成元组, 包含每个参与实体集的分量。例如,( Basic Instinct, Sharon Stone)就是联系Stars-in所对应的联系集中的一个元组。2. 2 实 体 联 系 图数 据 库建 模 有 一 种 图形 方 法, 称 为 实 体 联 系图 (entity-relationship diagram) , 它 和ODL 的面向对象的设计方法有很大的相似 性。实体联系( 即 E/ R) 图和最初讨论的 ODL一样具有 三个主要的部分 ( 虽 然E/R和ODL模型 都有我 们将在以 后讨论的 附加特 性) 。这些部分是:1. 实体集和类相似。实体是实体集的成员, 和ODL中的对象类似。2. 属性是描 述实体某个特性 的值。因此, E/ R 和 ODL 中的属 性本质上 具有相同 的概念。3. 联系是两个或多个实体集之间的连接。在 E/ R 模型中的联系和 ODL 中的联系十分类似。然而:( a) 在 E/ R 模型中, 我们 对两个方 向上 的联系 用一 个名 字, 而 在 ODL 中, 分 别称 为联系和反 向联系。例 如, 图 2. 6 中 的反 向联系Movie: :stars和St ar: :st arredIn在 E/ R 模型中将用单一的联系表示。(b) E/R模 型中 的联系 可以 涉及两 个以 上的 实体集, 而ODL的联 系最 多涉 及两 个类。例 2. 7 图 2. 8 是一个实体联系图, 它表示了和图 2. 6 用ODL说明的相同的现 实世界。实体集是 Movies( 电影) 、Stars( 影星) 和 Studios( 制片公司) 。我们应该用复数形式给实体集命名, 而通常用单数给类命名, 这说明了图 2. 8 和图 2. 6 在命名上的细微差别。电影的 实体集 Movies 和图 2. 6 的 类 Movie 具有 相同 的四个 属性: tit le、year、length·92·
图 2. 8 电影数据库的实体联系图和filmT ype。类似地, 另两个实体集具有与它们相应的ODL类所说明的name和address属性。在图 2. 8 中我们也看到E/R联系和图 2. 6 的ODL说明中的联系相对应。一个 联系是 Stars-in( 主演) , 它所体现的信息分别来自 ODL 的类 Movie 和 Star 的一对相反的联系stars和starredIn。 图 2. 8 中E/R联 系Owns( 拥 有) 代 表 了ODL的 两 个 反 向 联 系Movie: : ownedBy 和 Studio: : owns。图 2. 8 中指向实体集 Studios 的箭头表明每部电影都由唯一的制片公司所拥有。下面, 我们将讨论有关E/R图的多重性问题。2. 2. 1 E/ R 联系的多重 性正如我们在例 2. 7 中所看到的那样, 箭头可以用 来表示E/R图中联系的多重 性。如果一个联系是从 E 到 F 的多对一联系, 那么我们就画一个指向 F 的箭头。这个箭头表示实体集E中的每个实体, 最多和实体集F中的一个实体有关。然而,F中的一个实体可以和 E 中的多个实体有关。遵循这个原则, 实体集E和实体集F之间一对一的联系通过指向E和F两个实体的两个箭头来表示。例如, 图 2. 9 表示两个实体集 Studios 和 Presidents 以及它们之间的联系Runs( 经营) ( 把属性省略了) 。我们假定一个总裁只能经营一个制片公司, 而一个制片公司也只有一个总裁, 所以这个联系是一对一的, 用两个箭头表示, 每个箭头指向一个实体集。图 2. 9 一对一的联系2. 2. 2 联系的 多向性和 ODL 不同, E/ R 模型使得定义 涉及两个以上实 体集的联系更为 方便。然而, 实 际上, 三元( 三向) 或更 多元的联系是很 少的。E / R 图中的多向联 系通过从菱形 的联系到 所涉及的每个实体集之间的连线来表示。例 2. 8 图 2. 10 是一个涉及到制片公司、影星和电影的联系 Contracts( 签约) 。该联·03·
在多向联系中对箭头符号的限制在有三个或多 个参与者的联 系中, 对其连 线上的箭 头或非箭 头没有 足够的选 择。因此, 我们不可能通过箭头描述每一种可能的情形。例如, 在图 2. 10 中, 制片公司实际上是电影单独 的函数, 而 不是影星和电 影共同 的函数, 因为只 要有一 个制片公 司就能制作电影。然而, 我们的符号不能将这种情况和一种三向联系的情况相区别, 在这种三向联系中由箭头所指的实体集确实是另外两个实体集的函数。在 3. 5 节, 我们将提出一种正式的表示法—— 函数依赖—— 它有足够的能力来描述所有可能的选择。图 2. 10 三向联系系表 示 一个 制 片公 司 和一 个 特定 的 影 星签约来表演 一部特定 的电影。一 般来说,可以把E/R联 系的值 想象成 元组 的联 系集, 而 元 组 的 分 量 就 是 参 与 该 联 系 的 实体, 正如我们在方 框“E/R联系 的可视化”中所讨论的那样。这样, 联系 Contracts 就可以用三元组的形式来描述:( studio, star, movie)在多向联系中, 指向实体集E的箭头意味着 如果我们从该联系 的每个其他实体 集中选择一个实体, 那么这些实体将 和 E 中唯一的 实体相关。( 注意, 这 个定义简单地把 我们在双向联系中 使用的多重性概 念加以推广。) 图 2. 10 中, 有一个指向实 体集Studios的 箭头, 表明对 于特 定的 star 和 movie 来说, 该 影星 为演该 电影 只和 一个制 片公 司签约。 然而, 却没有指向实体集st ars和movies的箭头。这意味着, 一个制片公司可以为一部电影和几个影星签约, 而一个影星可以和一个制片公司签约主演多部电影。 □2. 2. 3 联系中 的角色在一个联系中, 一个实体集可能出现两次或多次。如果是这样, 那么一个实体集在联图 2. 11 具有角色的联系系中出现多少次我们就从联系到 这个实体集画多 少条线。到实体集的每条线代表该实 体集所扮演的不 同角色。我们把名字标记在实体集和联系之间连线的侧面,并称它为“角色”。例 2. 9 图 2. 11 给出了实 体集 Movies 和它本 身的联系Sequel-of。每个联系都在两部电 影之间, 其中一部是另一部的续集。为了区别联系中的两部电影, 一条线标记为角色Original( 首 集) , 另一条线标 记为角色Sequel( 续集 ) , 分别表示电影 的首集及其续集。我们假定一部电影可能有多部续集, 但是对于每部续集都只有一部首集。因此, 该联系是 从续集电 影到首 集电影的 多对一 联系, 正如图 2. 11 的E/R图中 的箭头所 表明的那样。 □例 2. 10 我们在较早的例 2. 8 中曾引入 Contracts( 签约) 联系, 在图 2. 12 又给出 了·13·
该联系的更复杂的形式, 把它作为最后一个例子, 它既包括多向联系又包括具有多重角色的实体集。现在, 联系Contracts涉及两个制片公司、一个影星和一部电影。意图是一个和某影星已有签 约的制片公司可 以再和第二个 制片公司 签约, 让这个 影星在特 定的电影 中扮演角色。这样, 该联系可以通过如下形式的四元组来描述:( st udio1, st udio2, star, movie)这意味着第二 个制片公司和第 一个制片公司 签约, 让第一个 制片公 司的影星 为第二个 制片公司演这部电影。在图 2. 12中 我 们 看 到 指 向Studios的 箭 头 扮 演 着 两 个 角 色 , 一 个 是 影 星 的“ 所 有图 2. 12 四向联系者”, 一个是 电 影 的制 作 者。然 而, 没有 指 向Stars或Movies的箭头。理由如下: 给定一个影星、一部 电影 和一 个制 作 该电 影的 制片 公司, 在 这里它 只能 是“拥有”该影 星的 唯一 的制片公司。( 我们假定一个影星正好和一个制片公 司签 约。) 类 似地, 只 有 一个 制片 公司 制作给定的电影, 所以给定一 个影星、一 部电影和该 影星所 在的 制片 公司, 我们 就可 以确 定唯一的制作电影的制片 公司(Producing studio) 。注意, 在两种情 况下, 实 际上, 我们 只需要一个其他的 实体就能确定这 个唯一的 实体—— 例如, 我们 只要知 道电影就 可以确定 唯一的制作电影的制片公司—— 但是这个事实并不改变多向联系的多重性的规定。图中没有指向Stars或Movies的 箭头。给定一个影 星、该影星 所在的制片公司 和制作电影的制片公司, 可能有几个不同的签约使得该影星在几部电影中扮演角色。这样, 联系四元组的其他三个分量不能唯一地确定一部电影。类似地, 制作电影的制片公司可能和某个其他的制片公司签约在一部电影中使用多个影星。这样, 联系的其他三个分量就不能确定一个影星。 □2. 2. 4 联系中 的属性把属性和联系相连, 而不是和任何一个与联系相连的实体集相连, 有时更为方便。例如, 考虑图 2. 10 中的联系, 它表示影星和制片公司之间为一部电影而签约。我们可能希望记录和这个签约有关的酬金。然而, 我们不能把它和这个影星相连; 因为一个影星可能从几部不同的电影中得到不同的酬金。类似地, 把酬金和一个制片公司相连( 制片公司可能对不同的影星支付不同的酬金) 或者和一部电影相连( 一部电影中不同的影星可能得到不同的酬金) 都是没有意义的。然而, 把酬金和Contracts联系所对应的联系集中的三元组( st ar, moie, studio)相连是合适的。由于加上属性, 图 2. 13 看起来比图 2. 10 更加充实。联系有属性salary( 酬金) , 而实体集的属性和图 2. 8 中给出的属性相同。把属性放在联系上决不是必须的。作为代替, 我们可以建立一个新的实体集, 让它的·23·
实体具有属于联系的属性。然后, 如果我们在联系中包含该实体集, 那么就可以忽略联系本身的属性。图 2. 13 具有属性的联系例 2. 11 让我们修改图 2. 13 中的E/R图, 该图在Contract联系上有属性salary。作为代替, 我们建立一个实体集 Salaries, 具有属性 salary。Salaries 成为联系 Contracts 的第四个实体集。图 2. 14 中给出了完整的图。 □图 2. 14 将属性移到实体集2. 2. 5 把多向 联系转换 成二元联系回忆一下, 与 E / R 模型不同, ODL 把我们限制在二元联系中。然而, 任何连接多于两个实体集的联系都可以转换成二元、多对一的联系的聚集, 而不丢失任何信息。在 E/ R 模型中, 我们可以引入一个新的实体集, 并把它的实体看作是多向联系所对应的联系集的元组。我们称这个实体集为连接实体集。然后, 我们引入从连接实体集到为最初的多向联系提供元组分量的每个实体集的多对一联系。如果一个实体集扮演多个角色, 那么它将是每个角色所对应的一个联系的目标。例 2. 12 图 2. 12 中的四向联系签约 Contracts 可以由也称作签约 Contracts 的实体集来取代。正如在图 2. 15 所看到的那样, 它参与四个联系。如果联系Contracts所对应的联系集有一个四元组·33·
( st udio1, st udio2, star, movie)那么实体集Contracts( 签约) 会有一个实体e。该实体通过联系Star-of( 签约的影星) 与实体集 St ars 中的实 体 star 相连。它通过 联系 Movie-of( 签约的 电影) 和 Movies 中的实 体movie相连。它通过联系St udio-of-st ar( 拥有影星的制片公司) 和Producing-st udio( 制作电影的制片公司) 分别和 Stu dios 的实体 st udio1 和 studio2 相连。注意, 我们 已经 假定 实体集Contracts没有属 性, 不过 图 2. 15 中 的其 他 实体 集有 未给出的属性。然而, 向实体集 Contracts 中增加属性, 例如签约日期, 是可能的。 □图 2. 15 用实体集和二元联系代替多向联系在ODL中, 我们将用和以上描述的对E/R模型的转换相似的方式表示像图 2. 12 那样的多向联系。然而, 由于 ODL 中没有多向联系, 因此转换不是可有可无的, 而是必不可少的。例 2. 13 假定 我们有类Star,Movie和Studio, 对应 于图 2. 12 中的三个实体集 中的每一个。为了表示四向联系Contracts, 我们引入一个新的类, 比如Contract。这个类没有属性, 但是它有四个联系, 对应于 E/ R 联系的四个分量。图 2. 16 中给出了 ODL 说明, 图中省略了反向联系。E/R联系Contracts中的每一个四元组对应于ODL类Contract中的一个对象。 □ inter kface Contr act {relationship Studio ownerOfStar;relationship Studio producingStudio;relationship Star star;relationship Movie movie;};图 2. 16 用ODL表示签约2. 2. 6 本节练 习* 练习 2. 2. 1: 用E/R模型表达练习 2. 1. 1 的银行数据库。务必在适当的地方画上箭头,·43·
2. 2. 1 E/ R 联系的多重性 30……………………………………………………………………2. 2. 2 联系的多向性 30…………………………………………………………………………2. 2. 3 联系中的角色 31…………………………………………………………………………2. 2. 4 联系中的属性 32…………………………………………………………………………2. 2. 5 把多向联系转换成二元联系 33…………………………………………………………2. 2. 6 本节练习 34………………………………………………………………………………2. 3 设计原则 35………………………………………………………………………………………2. 3. 1 真实性 36…………………………………………………………………………………2. 3. 2 避免冗余 36………………………………………………………………………………2. 3. 3 对简单性的考虑 36………………………………………………………………………2. 3. 4 选择合适的元素类型 37…………………………………………………………………2. 3. 5 本节练习 38………………………………………………………………………………2. 4 子类 40……………………………………………………………………………………………2. 4. 1 ODL 中的子类 40…………………………………………………………………………2. 4. 2 在 ODL 中的多重继承 40………………………………………………………………2. 4. 3 实体联系图中的子类 42…………………………………………………………………2. 4. 4 E/R模型中的继承 42……………………………………………………………………2. 4. 5 本节练习 43………………………………………………………………………………2. 5 对约束的建模 44…………………………………………………………………………………2. 5. 1 键码 45……………………………………………………………………………………2. 5. 2 在 ODL 中说明键码 46…………………………………………………………………2. 5. 3 在E/R模型中表示键码 47………………………………………………………………2. 5. 4 单值约束 47………………………………………………………………………………2. 5. 5 参照完整性 48……………………………………………………………………………2. 5. 6 E/R图中的参照完整性 48………………………………………………………………2. 5. 7 其他类型的约束 49………………………………………………………………………2. 5. 8 本节练习 49………………………………………………………………………………2. 6 弱实体集 50………………………………………………………………………………………2. 6. 1 产生弱实体集的原因 50…………………………………………………………………2. 6. 2 对弱实体集的要求 52……………………………………………………………………2. 6. 3 弱实体集的表示法 52……………………………………………………………………2. 6. 4 本节练习 53………………………………………………………………………………2. 7 历史上有影响的模型 53…………………………………………………………………………2. 7. 1 网状模型 53………………………………………………………………………………2. 7. 2 网状模式的表示 54………………………………………………………………………2. 7. 3 层次模型 55………………………………………………………………………………2. 7. 4 本节练习 56………………………………………………………………………………2. 8 本章总结 56………………………………………………………………………………………2. 9 本章参考文献 57…………………………………………………………………………………第 3 章 关系数据模型 58…………………………………………………………………………………3. 1 关系模型的基本概念 58…………………………………………………………………………·Ⅳ·
并表明联系的多向性。练习 2. 2. 2: 考虑到练习 2. 1. 2 中提到的变化, 修改你在练习 2. 2. 1 对帐户的解答:( a) 修改你的图, 使一个帐号只能有一个顾客。(b) 进一步修改你的图使一个顾客只能有一个帐号。! ( c) '修改你在练习 2. 2. 1 中的原始图, 使顾客可以有一个地址 集合( 它 是街道-城市-州的三元组) 和一个电话集合。记住, 尽管在有限的情况下ODL允许属性为聚集类型, 但是在 E/ R 模型中是不允许的。! (d) 2进一步修改 你的图, 使顾客能有一个地 址集合, 并且在每 个地址中 有一个电 话集合。练习 2. 2. 3: 用E/R模型表达练习 2. 1. 3 的球队/ 队员/ 球迷数据库。记住, 颜色的集合不是球队的合适的属性类型。你如何避免这种限制?! 练习 2. 2. 4: 假定我 们希望把两个队 员和一个球队之间 的联系Led-by加到 练习 2. 2. 3的模式中。意图是这个联系集包括三元组(player1,player2,team)以便当某个其他的 player2 是队长时 player 1 暂时在球队中比赛。(a) 画出对E/R图的修改。( b) 用一个新的实体集和二元联系替代你的三元联系。! (c) '你的新的二元联系是否和以前存在 的任何联系相同? 注意, 我们假定两个 队员是不同的, 也就是, 队长不能领导自己。练习 2. 2. 5: 修改练习 2. 2. 3 中的E/R图以包括队员的历史, 像练习 2. 1. 4 那样。! 练习 2. 2. 6: 用 E / R 模型表达练习 2. 1. 5 和 2. 1. 6 有关“人”的 数据库。模型中包 括对于母亲、父亲和孩子的联 系; 当一 个实体集在 一个联系 中多次使 用时不 要忘记标 明角色。模型中还包括每个人获得学位的信息, 如练习 2. 1. 6 所描述的那样。标明每个联系的多重性。你是否需要对于母亲、父亲和孩子的单独的联系? 为什么要或为什么不要?练习 2. 2. 7: 表示 练习 2. 1. 5 中的 信息的另 一条途 径是有一 个三元 联系 Family, 打算 让Family对应的联系集中的三元组( person, mother , father)是某个人、他的母亲和他的父亲; 当然, 所有三个人都在实体集People中。* ( a) 画出这个图( 不需要关于教育的信息) 。在适当的位置放上箭头。(b) 3用一个实体集和二元联系替代三 元联系Family。再一次 放上箭头来表明 联系的多重性。练习 2. 2. 8: 用E/R模型表达你在练习 2. 1. 7 设计的大学数据库。2. 3 设 计 原 则我们还需要 学习关于ODL或E/R模型 的许多细 节, 但 是我们 有足够的 基础开始 研究什么是好的设计和应当避免什么等关键问题。在这一节, 我们将努力阐明和详细描述某些有用的原则。·53·
冗余和反向联系我们可以认 为在ODL中使用联系 及其反向联 系是冗余 设计的一 个例子。然 而,我们不应当假 定要表示联系及 其反向联系 就要用 两个不同 的数据 结构, 例如, 一个方向上的指针和另一个方向上的指针。回忆一下, 联系及其反向联系的定义仅仅反映了这样的事实, 即人们原则上可以在任一方向上遍历一个联系。然而, 如 果我们选择用两 个单独的 数据结 构实现联 系, 那 么我们 确实冒着 冗余所带来 的危 险。因为 当数据 改变 时, 希望底 层的 指针 始终保 持不 变, 因此 基于 ODL 的DBMS的实现者必须注意如何进行数据库的更新。但是, 这是系统层的问题。这里有一个假定: 实现者最终将使问题得到正确解决。到那时, 在实现层由于冗余而带来的危险很少, 而在两个方向上都存在指针将导致以很快的速度实现对联系的遍历。2. 3. 1 真实性首先, 设计应当忠于规范。也就是说, 类或实体集和它们的属性应当反映现实。你不能给影星 附加一个属性 number-of-cylinders( 汽缸数) , 尽 管它对实 体集或 类 Automobile( 汽车) 有意义。如果我们了解要建模的那部分现实世界, 那么无论声明什么样的联系手段都将是有意义的。例 2. 14 如果我们定义一个在Star和Movie之间的联 系St ars-in, 它将是多对 多的联系。原因是对现实世界的观察告诉我们影星可以在多部电影中出现, 而电影也可以有多个影星。将联系 Stars-in 说明成在任一方向上多对一或说明成一对一都是不正确的。2. 3. 2 避免冗 余我们应当注意任何事物只表达一次。例如, 我们已经使用了电影和制片公司之间的联系Owns( 拥有) 。我们还可以让实体集Movies有属性St udioName。虽然这么做没有什么不合法的, 但由于以下几个原因, 这种做法是危险的:1. 两次表示“所属的制片公司”这一同样的事实, 比任何一次单 独的表示都占用 了更多的空间。2. 如 果一 部 电影 被卖 掉了, 我 们可 能 通过 Owns 改变 与该 电影 有 关的 所 属制 片 公司, 却忘记改变该电影的StudioName属性值, 反之亦然。当然, 人们可以强调决不应做这样粗心大意的 事, 但是实 际上, 错 误是频繁的, 因为 企图用两 种不同 的方式表 达同一个 事物, 给我们带来了麻烦。这些问题将在 3. 7 节正式描述, 并且我们也将学习一些工具来重新设计数据库模式,于是冗余以及随之而来的问题将迎刃而解。2. 3. 3 对简单 性的考虑避免在设计中引入过多的元素是绝对必要的。例 2. 15 我们假定存 在“电影所 有权”, 表 示一部电 影的所 有权(holding) , 用来代 替·63·
电影和制片公司之间的联系。那么我们可以建立另一个类或实体集 Holdings。可以在每部电 影 和 代 表 该 电 影 的 唯 一 所 有 权 之 间 建 立 一 对 一 的 联 系Represents( 代 表) 。 从Holdings 到 Studios 是多对一联系, 如图 2. 17 所示。图 2. 17 具有多余实体集的不良设计在技术上, 图 2. 17 的结构确实代表了现实世 界, 因为经 由Holdings从一部电影 到它唯一所属的制片公司是可能的。然而, Holdings 没有起有效的作用, 而没有它会更好。它使得利用电影-制片公司之间联系的程序更加复杂, 浪费空间, 而且容易出现错误。 □2. 3. 4 选择合 适的元素 类型有时, 可以选择用来表示现实世界概念的设计元素的类型。许多这样的选择是在使用属性还是使用类或实体集之间进行的。一般来说, 属性比类/ 实体集或联系实现起来更为简单。然而, 让一切事物都成为属性通常会给我们带来麻烦。例 2. 16 让我们考虑一个特殊的问题。在图 2. 6 或 图 2. 8 中, 我们使制片公司 为类或实体集是否明智? 我们是否应该删除制片公司的类或实体集而把制片公司的名字和地址作为电影的属性?这样做带来的一个问题就是对于每部电影都要重复制片公司的地址。这就带来了冗余; 除了 2. 3. 2 节所讨论的冗余的缺点之外, 如果一个给定的制片公司不拥有任何一部电影, 那么我们将面临失去该制片公司地址的危险。另一方面, 如果我们不记录制片公司的地址, 那么把制片公司的名字作为电影的属性并没有任何损害。我们没有地址重复带来的冗余。我们必须对每部为 Disney( 迪斯尼) 所拥有的电影给出制片公司例如迪斯尼的名字, 这种情况不是真正的冗余, 因为我们必须以某种方式表示每部电影的拥有者, 而给出名字正是一种合理的方式。一般来说, 我们建议, 如果某个事 物具有 比它的名 字更多的 有关信 息, 那 么可能需 要的是实体集或类。然而, 如果它为设计提供的只有它的名字, 那么作为属性可能更好。这种差别和将在 3. 7 节介绍的关系模型的模式“规范化”问题密切相关。例 2. 17 让我们考虑如下的观点: 使用一个 多向联系和使用具 有几个二元联系 的连接实体集之间有一个折衷。我们在图 2. 12 看到一个影星、一部电影和两个制片公司之间的四向联系Contracts( 签约) , 我们机械地将它转换成实体集Contracts。我们选择哪一个是否有影响?在 ODL 的设计中, 我们其实没有选择的余地, 因为并没有提供多向联系。在 E/ R 模型中, 二者均合适。然而, 如果我们对这个问题稍加改动, 那么几乎会强制我们选择连接实体集。让我们假定签约涉及一个影星、一部电影, 以及任何制片公司集合, 而不是一个制片公司。这种情况比图 2. 12 中的情况更为复杂, 在那里有两个制片公司扮演两个角色。在这种 情况下, 我 们会 涉及任 意数 量的制 片公 司, 或许一 个做 产品, 一个 搞特 技, 一 个管 销售, 等等。这样, 我们不能对制片公司分配角色。·73·
看来, 和联系 Contracts 对应的联系集必须包括如下形式的元组(st ar( 影星) ,movie( 电影) ,set-of-st udios( 制片公司的( 集合) )并且联系 Contract s( 签约) 本身不仅涉及通常的影星和电 影实体集, 而且涉及一个其 实体为制片公司集合的新的实体集。尽管这种方法是允许的, 但是把制片公司的集合作为基本实体看来并不自然, 因此我们并不推荐它。一个 更好的 方法是将Contract s( 签约 ) 作 为实体集。 如图 2. 15, 一个实 体Contracts连接一个影星、一部 电影 和一 个 制片 公 司的 集 合, 但 是现 在不 必限 制制 片公 司的 数 量。图 2. 18 连接影星、电影和制片公司集合的签约于是, 在 签约 和制 片 公司 之间 的联 系是 多 对多的, 而不 是多 对 一的, 然 而, 如果 签约 是 真正 的“连接”实体 集, 那 么它将 是多对 一的。 图 2. 18画出了 E/ R 图。注意, 一 个签 约只与 单个 影星和 一 部 电 影 有 关, 而 与 任 何 数 量 的 制 片 公 司有关。相 同的 设计 策略 对ODL也 完全 适 用。例如, 以下的 ODL 接口说明:interface Contract{relationship Star theSt ar;relationship Movie theMovie;relationship Set < Studio> studios;};是合适的。在这 里, 签约对 象有三个联系, 分 别到一个 影星、一部电 影和一个 制片公司 集合。反向联系省略。 □2. 3. 5 本节练 习* 练习 2. 3. 1: 图 2. 19 是一个涉及顾客和 帐户的银行数据 库的 ODL 设计。假定各 种联系和属性的意义正如给出的名字所期望的那样。对这个设计做出评价。它违背了哪些设计规则? 为什么? 你建议做哪些修改?! ! 练习 2. 3. 2: 在本练 习和以下 的练习 中我 们将考 虑用E/R模型描 述出 生日期 的两 种设计选择。一个出生涉及到一个婴儿( 双生儿将用两个出生表示) 、一位母亲、任意数量的护士和任意数量的医生。因此, 假定我们有实体集Babies( 婴儿) 、Mothers( 母亲) 、Nurses( 护 士) 和 Doctors( 医生) 。假定 我们 还用联 系 Births( 出生 ) 和这 四个 实体 集 相连, 如 图2. 20所建议的那样。注意, 和Births对应的联系集的元组具有如下形式( baby, mother, nurse, doctor)如果 有多个 护士 和/ 或 医生给 一个 婴儿接 生, 那么 将有 几个 元组 有相 同 的婴 儿和 母亲, 而一个元组对应护士和医生的一种组合。我们可能希望把某些假设体现在我们的设计中。为了表示每种假设, 说出如何把箭头或其他元素加到 E/ R 图中。(a) 对于每个婴儿, 有唯一的母亲。·83·
( b) 对于婴儿、护士和医生的每种组合, 有唯一的母亲。(c) 对于婴儿和母亲的每种组合, 有唯一的医生。! 练习 2. 3. 3: 解决练 习 2. 3. 2 的问 题的另一 种方法 是通过实 体集 Births 连 接四个实 体集Babies( 婴儿) 、Mothers( 母亲) 、Nurses( 护 士) 和Doctors( 医生) , 共有四 个联系,Births和每个其他实体集之间有一个联系, 如图 2. 21 所示。用箭头( 指明其中某些联系是多对一的) 来表示以下情况:( a) 每个婴儿是唯一的出生的结果, 并且每个出生属于唯一的婴儿。(b) 在(a) 的条件外, 还要求每个婴儿有唯一的母亲。( c) 在( a) 和( b) 的条件外, 还要求对于每个出生有唯一的医生。 inter Tface Address {attribute string addr;relationship Set<Customer>residentsinverse Customer: :livesAt;};interTface Customer{attribute string name;relationship A ddress livesAtinverse Addr ess : : residents;relationship AcctSet accountsinverse AcctSet: :owner;};interTface Account{attribute real balance;relationship Se t < AcctSet> memberOfinverse AcctSet : : members;};inter Tface AcctSet {attribute string ownerAddress;relationship?Customer ownerinver se Customer: :accounts;relationship?Set<Account>membersinver se Account : : memberOf;};图 2. 19 对银行数据库的不良设计图 2. 20 用多向联系表示出生 图 2. 21 用实体集表示出生·93·
! ! 练习 2. 3. 4: 假 定 我们 允许 一个 出生 涉 及由 一个 母亲 生的 多 个婴 儿。你如 何用 练 习2. 3. 2和 2. 3. 3 的方法表示每个婴儿仍然有唯一的母亲这一事实?! 练习 2. 3. 5: 用 ODL 重做练习 2. 3. 2 和 2. 3. 3 的 E / R 设计。你发现这些练习的哪些情况容易实施? 哪些情况不能实施? 如何修改你的设计允许像练习 2. 3. 4 那样生多个婴儿?2. 4 子 类类往往还包括某些具有附加特性的对象, 而这些特性并不和类的所有成员相关。如果是这样, 我们发现一种有效的方法就是将这个类组织成子类, 除了作为整体的类的特性以外, 每个子类有它自己的附加属性和/ 或联系。ODL有一 个简单的方法说 明子类, 我 们将在下面予以讨论。然后我们再看看 E/ R 模型如何用称为“属于”(“isa”) 联系( 也就是,“anA is a B”表示 子类A属于 类B的“属于”(“isa”) 联系 ) 的特殊 联系, 表 示类-子类 的层 次关系。2. 4. 1 ODL中的子类可能存储在我 们的实例数据库 中的各 种电影包 括卡通片、谋 杀片、惊险片、喜剧片 以及多种其他特殊类型的电影。对于每种电影类型, 我们可以定义一个在例 2. 1 中介绍的类Movie的子类。我们通过在类C说明的类名C后面加上冒号和另一个类D的名字来定义类 C 是类 D 的子类。例 2. 18 我们可 以说 明Cartoon为Movie的子 类, 于是Movie是Cartoon的 超类,用以下的 ODL 说明:1) interfaceQCart oon:Movie{2) relationship Set < Star> voices;};其中, 1) 行说明 Cartoon 为 Movie 的子类。2) 行表明所有的 Cart oon 对象都有一个联系voices, 代表为卡通人物配音的人。我们没有指明 联系voices的反向联 系, 虽然技 术上我们可以这 样做。注意, 联 系 voices 并不是对所 有电影都 有意义, 只有 卡通片才 有意义,所以我们不想把voices作为类Movie的联系。子类继承其超类( 也就是子类从中派生的类) 的所有特性。也就是说, 超类的每个属性或联系自动成为子类的属性或联系。这样, 在例 2. 18 的每个卡通片对象都有从Movie中继承下来 的属性 t itile, year , length 和 filmT ype( 回忆一下 图 2. 6) , 并从 Movie 中继承 了联系stars和ownedBy, 此外, 还有它自己的联系voices。2. 4. 2 在 ODL 中的多重继 承类可以有多个子类, 像 2. 4. 1 节所描述的那样, 每个子类从它的超类继承特性。进而,子类本身可能还有子类, 形成类的层次, 而每个类都继承祖先的特性。一个类也可能有多个超类。下例说明多重超类的隐患和问题。例 2. 19 我们可以为谋杀片定义类Movie的另一个子类:·04·
1) interf ace MurderMystery: Movie {2)att ribute string weapon;};这样, 所有的谋杀片除了所有电影都有的四个属性和两个联系以外, 还有一个指明谋杀武器的属性。现在, 考虑像《谁陷害了兔子 罗杰?》(Who F ramed Roger Rabbit?) 这样的电 影, 它 既是卡通片, 又是谋杀片。这 样的电影除了有 普通电影的 特性以 外还应有 联系 voices 和 属性weapon。我 们可以通过说明另 一个子类Cartoon-MurderMystery( 卡通-谋杀 片) 来 描述这种情况, 它是 Cartoon( 卡通片) 和 MurderMystery( 谋杀片) 两者的子类。说明如下:interface Cartoon-MurderMyst ery:Cartoon,MurderMyst ery{};这样 的 定 义就 使 Cartoon-MurderMystery 对象 具 有 Cart oon 和 MurderMystery 两个子类的所 有特性。我 们没有说 明任何只 属于Cartoon-MurderMystery的属性 或联系。类 Cartoon-MurderMystery 的对象从类 MurderMystery 中 继承属性 weapon( 武 器) 并 从Cart oon中 继 承 联 系voices( 配 音) 。另 外, 因 为 类MurderMystery和Cartoon都 从 类Movie 中 继承了四 个属性 和两个联 系, 因 此类 Cartoon-MurderMystery 也 继承了 这六 个特性。然而, 类Cart oon-MurderMystery并没有继承这六个特性的两个副本; 而是经由它的两个直接 超类中的任何一个 从 Movie 中继承这些 特性。图 2. 22 说明了涉 及这四个 类的子类-超类联系。 □图 2. 22 表示多重继承的图通常, 我们可以在接口 名 C 的说明之 后加上冒 号和任意 数量 的其 他类 的列 表 来说 明类C为这 些其 他类 的子类。 例 2. 19 的 Cartoon-MurderMyst ery 的说 明就 是这种格式的 一个实例。当类C从几个 类中继 承时, 特性名之间有发生冲突的隐患。类 C 的两个或多个超类可能有同名的属性或联系, 而这些特性的类型可能不同。例 2. 20 假 定 我 们 有 称 为 Romance ( 言 情 片 ) 和Courtroom( 刑侦片) 的类Movie的子类。进而假定这两个子类中每个都有称为ending( 结局 ) 的 属 性。 在 类 Romance 中, 属 性 结 局 从 枚 举 类 型 { 喜 剧, 悲 剧 } 中 取 值, 而 在 类Courtroom中, 属性结 局 从 枚 举 类 型 {有 罪, 无 罪 }中 取 值。 如果 我 们 再 建 立 一 个 子 类,Courtroom-Romance, 它 把 Romance 和 Courtroom 两 者 都 作 为 超 类, 那 么 在 类Courtroom-Romance中继承的属性结局其类型含糊不清。 □虽然 ODL 本身并不 定义特定的语法, 但是 ODL 的实现将至少提供以 下机制之一 来使用户能够规定如何处理由于多重继承而产生的冲突:1. 指出特性的两个定义中哪一个用于子类。例如, 在例 2. 20 中, 我们可以决定 在类刑侦-言情片中, 与其 说关心 法庭的审 判结果 不如说更 关心电影 的结局 是喜剧还 是悲剧。在这 种 情 况下, 我 们 将 规 定 类 Courtroom-Romance 从超 类 Romance 中, 而不 是 从 超 类Courtroom中, 继承属性ending( 结局) 。2. 在类 C 中, 对于有相同名字的另一个特性给一个新的名字。例如, 在例 2. 20 中, 如果Courtroom-Romance从 超 类Romance中 继 承 属 性ending, 那 么 我 们 可 以 规 定 类·14·
Courtroom-Romance 有一个称为 verdict( 判决) 的附加属性, 这是把从类 Courtroom 继承的属性ending改名了。3. 为类 C 重新 定义在 它的一个 或多 个超类 中已 定义的 某些 特性。例 如, 在例 2. 20中, 我们可以决定属性ending不直接从任何一个超类中继承。相反, 对于刑侦-言情片, 我们重新定义属性结局( ending) 为整数, 表示从观众投票结果得出的对电影结局的满意程度。注 意, 即 使 在 例 2. 19 中 也 有 冲 突:Cartoon-MurderMyst ery从 每 个 直 接 的 超 类( Cartoon 和 MurderMystery) 中继承所有的六个特性, 例如 t itle( 名称) 和 stars( 影星) , 而这些特性是 这两个类从类Movie中继承下来的。然而, 因为名 称和其他特性 的定义在 超类 Cartoon 和 MurderMystery 中是相同的, 因此无论决定用哪种定义都是可以接受的。2. 4. 3 实体联 系图中的 子类回忆一下,ODL中的类和E/R模型中的实体集类似。假定类C是类D的子类。为了在 E/ R 模 型中表 达这个概 念, 我 们通过 称为“属于”( isa) 的 特殊联 系使 对应 于类 C 和 D的两个实体集相连。我们为实体集C和D画普通的方框。任何只和实体C有关的属性或联系都连到方框 C 上。用于 C 和 D 两者的属性则放在 D 上。Isa联系用两条边连 同中间一个 三角形 来表示。三 角形的顶 点应指向 超类。专用 词“isa”可随意地放在三角形中。例 2. 21 实体集Movie及其两个子类Cartoon和MurderMystery如图 2. 23 所示。该子类结构和例 2. 19 用ODL表示的子类结构类似。标有isa的两个三角形分别从Cartoon和 MurderMystery 指向超类 Movies。我们没有给出把 Movies 与 Stars 和 Studios 相连的联系Stars-in和Owns, 我们只提供了连接Cartoon和stars的联系Voices( 配音) 。 □图 2. 23 E/R图中的属于(isa) 联系2. 4. 4 E/R模型中的继 承ODL或其他 面向 对象的 模型 和E/R模 型在 继承 的概 念上 有 细微 的差 别。在ODL中, 对 象 必 须 恰 好 是 一 个 类 的 成 员。 于 是, 在 例 2. 19 我 们 需 要 定 义 类 Cartoon-MurderMystery包含那些既是卡通片又是谋杀片的对象。例如, 我们不能把对象“兔子罗杰”既放在卡通片类中又放在谋杀片类中。·24·
在 E / R 模型中, 我们 将认为 一个实体 具有属 于几个实 体集的分 量, 而这几个 实体 集是单一的属于(isa) 层次的组成部分。Isa联系把各分量连成单一的实体。这个实体有它的所有分量的全部属性, 并参与它的分量所参与的所有联系。通常, 这 个 观点 的 效果 和 ODL 的 效 果相 同, 因 为 特性 的 继承 性 给 对象 的 属性 和 联系, 将和对应于该对象的实体从其分量中汇集的属性和联系相同。然而, 有一个差别, 我们将在例 2. 22 中讨论。在 3. 4 节, 当我们将ODL和E/R设计转换成关系模型时, 我们将看到另一个差别。例 2. 22 注意, 在图 2. 23 中我们不需要和卡通-谋杀片相对应的实体集。原因是, 这样一个实 体, 如 兔子 罗 杰、有 属 于 所 有三 个 实 体 集Movies( 电 影) 、Cartoons( 卡 通 片) 和MurderMystery( 谋杀片) 的分量。通过 isa( 属于) 联系三个分量连成一个实体。同时, 这些分量把 Movies 的所有四 个属性( 以及图 2. 23 中未画出的 Movies 的两个 联系) 都给了 实体 兔子 罗杰, 加上 实体 集MurderMystery的属 性weapon( 武器 ) 和实 体集Cart oon的 联系Voices( 配 音 ) 。 这 些 恰 好 是 例 2. 19 中 的 对 象 兔 子 罗 杰 所 在 的 类Cartoon-MurderMystery 中的特性, 这些特性是从它 的超类 Movie, Cartoon 和 MurderMystery 中继承下来的。然而, 注意, 如果 有某 些特 性属 于 卡通-谋杀 片, 但 是既 不属 于卡 通片 也 不属 于谋 杀片, 那么我们 就需 要在 图 2. 23 中 有第 四个实 体集Cartoon-MurderMystery, 而这 些特 性( 属 性 和联 系 ) 将 连 向这 个 实 体 集。那 么, 实 体 兔 子 罗 杰 将 有第 四 个 分 量, 该 分 量 属 于Cart oon-MurderMystery并为实体兔子罗杰提供这些新的特性。2. 4. 5 本节练 习* 练习 2. 4. 1: 让我们考虑军舰的数据库, 并用ODL加以描述。每艘军舰有下列与之相关的信息:1. 它的名称。2. 它的排水量( 重量) , 以吨为单位。3. 它的类型, 如战列舰、驱逐舰。另外, 有下列具有某些其他信息的特殊类型的舰艇:1. 炮舰是携带大型火炮的舰艇, 例如战列舰或巡洋舰。对于这类舰艇, 我们希望记录主炮的数量和口径。2. 航空母舰携带飞机。对于航空母舰, 我们 希望记录飞行甲板 的长度和分派给 它们的航空大队的集合。3. 潜艇可以在水下航行。对于潜艇, 我们希望记录它们的最大安全深度。你可以假定不是炮舰或航空母舰就是潜艇。4. 攻击型航空母舰既是炮舰又是航空母舰, 并具有和二者相关的所有信息。①回答下列问题:·34·①可 能有人 会感 到奇怪 , 确 实存 在这样 的东西 。Ise和H yuga是 两艘日 本的 战 列舰, 1943 年 对其 进行 改装 使其具有一 个飞 行甲板 和覆盖 其后 半部分 的飞 机库。
( a) 对于各类的这种层次结构给出 ODL 设计。(b) 说明 如何表示攻击型 航空母舰Ise。它 有排水量 36 000 吨, 有 8 门 14 英寸的 火炮, 有 200 英尺长的飞行甲板并携带“1 和 2”两个航空大队。! ! 练习 2. 4. 2: 对于某些子类, 例如练习 2. 4. 1 中的攻击型航空母舰, 只有一种可能 的类型, 而对于其他子类, 例如炮舰, 却有几种类型, 例如, 战列舰和巡洋舰。这种情况是否产生某种形式的冗余? 如果是这样, 如何消除这种冗余?* 练习 2. 4. 3: 用 E/ R 模型重复练习 2. 4. 1。! 练习 2. 4. 4: 修 改 你在 练 习 2. 15 中 对“people”数 据库 的 设 计以 便 包 括 下列 特 殊 类 型的人:1. 女人。2. 男人。3. 作父母的人。你可能也想 区别某些其他类 型的人, 于是用 联系连 接人的适 当的子类。 给出你的 设计, 用:( a) ODL(b)E/R模型2. 5 对约束的建模现在, 我们 已经看到 如何使 用ODL的 类及 其特性 ( 包括属 性和 联系) 或 者使 用E/R模型中的实体集和联系为一部分现实世界建模。我们在建模过程中感兴趣的许多结构都可以用这两种表示法中的任意一种来表示。然而, 现实世界中还有其他重要的方面不能用前面学过的工具建模。这种附加信息往往对数据具有约束的性质, 它超出了由类、属性和联系的定义而在结构和类型方面产生的约束。以下是对常用约束的粗略分类。这里并不包括所有的约束类型。关于约束的其他内容可以在 4. 5 节关系代数和第 6 章SQL编程中找到。1. 键 码( keys) 是 在类的范 围内唯 一标识一 个对象或 者在 实体集 的范 围内唯 一标 识一个实体的属性或属性集。一个类中的两个对象在构成键码的属性集上取值不能相同。2. 单值约束( single-value constraints) 要求某个角色的值是唯一的。键码是单值约束的一个主要来源, 因为它们要求和键码属性值有关的类或实体集的其他属性值是唯一的。然而, 单值约束还有其他来源, 正如我们将要看到的那样。3. 参照 完整性 约束 (reference integrity constraints) 要求 由某 个对象 引 用的 值在 数据库中确实存在。参照完整性类似于传统程序中对悬挂指针的禁止。4. 域的约束 (domain constraint s) 要求 属性值必 须取自 特定值的 集合或者 处于特 定的范围内。我们将在 6. 3 节中涉及对 SQL 的域约束。5. 一般约束(general constraints) 是要求在数据库中保存的任意的断言。例如, 我们可能会要求对任何一部电影列出的影星不超过十个。我们将在 4. 5 和 6. 4 节看到一般约束表达语言。·44·
3. 1. 1 属性 59……………………………………………………………………………………3. 1. 2 模式 59……………………………………………………………………………………3. 1. 3 元组 59……………………………………………………………………………………3. 1. 4 域 60………………………………………………………………………………………3. 1. 5 关系的等价表示法 60……………………………………………………………………3. 1. 6 关系实例 61………………………………………………………………………………3. 1. 7 本节练习 62………………………………………………………………………………3. 2 从ODL设计到关系设计 62………………………………………………………………………3. 2. 1 从 ODL 属性到关系属性 63……………………………………………………………3. 2. 2 类中的非原子属性 63……………………………………………………………………3. 2. 3 其他类型构造符的表示 66………………………………………………………………3. 2. 4 单值联系的表示 67………………………………………………………………………3. 2. 5 多值联系的表示 68………………………………………………………………………3. 2. 6 假如没有键码 69…………………………………………………………………………3. 2. 7 联系与反向联系的表示 70………………………………………………………………3. 2. 8 本节练习 71………………………………………………………………………………3. 3 从E/R图到关系的设计 72………………………………………………………………………3. 3. 1 实体集到关系的转换 72…………………………………………………………………3. 3. 2 E/R联系到关系的转换 73………………………………………………………………3. 3. 3 处理弱实体集 75…………………………………………………………………………3. 3. 4 本节练习 77………………………………………………………………………………3. 4 子类结构到关系的转换 78………………………………………………………………………3. 4. 1 用关系表示 ODL 子类 78………………………………………………………………3. 4. 2 在关系模型中表示“属于”联系 79………………………………………………………3. 4. 3 方法的比较 80……………………………………………………………………………3. 4. 4 使用 NULL 值合并关系 80………………………………………………………………3. 4. 5 本节练习 81………………………………………………………………………………3. 5 函数依赖 82………………………………………………………………………………………3. 5. 1 函数依赖的定义 82………………………………………………………………………3. 5. 2 关系的键码 83……………………………………………………………………………3. 5. 3 超键码 84…………………………………………………………………………………3. 5. 4 寻找关系的键码 85………………………………………………………………………3. 5. 5 由ODL设计导出的关系的键码 86……………………………………………………3. 5. 6 本节练习 87………………………………………………………………………………3. 6 函数依赖规则 88…………………………………………………………………………………3. 6. 1 分解/ 合并规则 88………………………………………………………………………3. 6. 2 平凡依赖 89………………………………………………………………………………3. 6. 3 计算属性的闭包 90………………………………………………………………………3. 6. 4 传递规则 92………………………………………………………………………………3. 6. 5 函数依赖的闭包 93………………………………………………………………………3. 6. 6 本节练习 94………………………………………………………………………………3. 7 关系数据库模式设计 95…………………………………………………………………………·Ⅴ·
约束是模式的一部分我们可能将数据库看作只在某段时间存在, 并且因为没有两个对象在某个属性有相同的值就错误地决定该属性构成键码。例如, 当我们建立我们的电影数据库时, 我们可能 没有 同时输 入两 部同 名的电 影。然而, 如 果我 们在这 个初 步证 据的 基 础上 做出title 是类 Movie 的键码的 决定, 并且按 t itle 为 键码这样 的假设 为我 们的 数据库 设计存储结构, 那么我们将会发现我们自己不能向数据库中输入第二部King Kong电影。因此, 键码约束和一般约束都是数据库模式的一部分。数据库设计者把约束随着结构设计( 例如, 实体和联系) 一起说明。一旦说明了某种约束, 就不允许对数据库进行违背该约束的插入或修改。因此, 虽 然数据库的一个 特例可以 满足某 些约束, 但是只 有适用 于作为现 实世界正确模型的数据库的所有实例而由设计者标识的约束, 才是“真正的”约束。这样的约束可以由用户假定并用于数据库的存储结构。有几方面的约束是重要的。那些约束告诉我们为之建模的现实世界的有关方面的结构信息。例如, 键码使用户能毫无混淆地识别对象或实体。如果我们知道属性name是类Studio 的对象的键码, 那么当我们通过制片公司的名字引用它的对象时, 我们就知道我们在引用唯一的对象。另外, 知道存在唯一值就可节省空间和时间, 因为存储一个值比存储一个集合更容易, 即使集合只有一个成员。①参照完整性和键码还支持允许快速访问某个对象的某些存储结构。2. 5. 1 键码在 ODL 中, 类的键码是一个 或多个属性的集合 K , 于 是对于该类中给定 的任何两 个不同的对象O1 和O2 , 都有与O1 和O2 的键码K对应的两组不同的属性值。在E/R模型中, 键码完全相同, 只是“实体集”代替了“类”,“实体”代替了“对象”。例 2. 23 让我们考虑例 2. 1 的类Movie( 电影) 。人们可能首先假定属性title( 片名)本 身是 键码。 然而, 有 些 片名 已 经被 两 个或 多 个的 不 同 的电 影 重复 使 用了, 例如, KingKong。因此, 将title本身说明为键码是不明智的。如果这样做了, 那么我们将不能在数据库中包括两部名为 King Kong 的电影的信息。一个更好的选择将是选择两个属性tit le和year的集合作为键码。我们将冒着有两部同年制作 的同名电影的风 险( 这样两 部电影不能都 存储在 我们的数 据库中) , 但这是不 大可能的。对于其他两个 类, Star 和 Studio, 在 2. 1 节介绍过, 我们 必须再 一次认真 考虑什么 可以作为键码。对于制片公司, 合理的假定是不会有两个电影制片公司重名, 所以我们将把name 作为 Studio 类的键码。然而, 用影星的名字唯一标识影星却容易混淆。一般而言名字 并不 能用 于区别 人。然而, 传 统上 影星都 随意 地选 择“艺名”, 因此我 们可 能希 望看 到·54·①注 意一下 类似 的情况 , C 程序中 , 即 使链 表只含 有一个 整数 , 表 示整 数也比 表示整 数的 链表更 简单 。
name 也作为影星类的键码。否则, 我们可以选择一对属性 name 和 address 作为键码, 这将是令人满意的, 除非两个影星同名且住在相同的地址。 □例 2. 24 根据例 2. 23 的经验可能使我们认 为找出键码或者确 信属性的集合构 成键码是困难的。实际上, 情况通常非常简单。为设计数据库而对现实世界的环境建模时, 人们往往要在环境范围以外为重要的类建立有效键码。例如, 公司一般给所有雇员都分配雇员号, 并且认真选择使这些标识号为唯一的号码。这些标识号的一个意图是确保在公司数据库中每个雇员都可以和所有其他雇员相区别, 即使有几个雇员的名字是一样的。这样,雇员号属性就可以作为数据库中雇员的键码。在美国公司中, 通常每个雇员还有一个社会保险号。如果数据库有一个属性是社会保险号, 那么该属性也可作为雇员的键码。注意, 一个类的键码可有几种选择并没有错, 正如雇员类可有“雇员号”和社会保险号两个键码。建立一个属性的目的就是作为键码, 这种想法非常普遍。除了雇员号以外, 我们看到在大学用学号区别学生。我们看到驾驶员许可证号和机动车登记号在机动车部门分别区分驾驶员和机动车。毫无疑问, 读者可以找到许多这种属性的例子, 而建立这种属性的主要目的就是作为键码。 □2. 5. 2 在ODL中说明键码在ODL中 用关 键 字key或keys( 用 哪一 个无 关紧 要) 并 用构 成 键 码的 属 性跟 在 后面, 来说明一个或多个属性为一个类的键码。如果在键码中有多个属性, 那么属性表必须用括号括起来。键码说明必须紧跟在接口说明之后, 在左花括号或者任何属性或联系之前说明。说明本身用圆括号括起来。例 2. 25 为了 说明两个 属性tit le和year的 集合构成 类Movie的 键码, 我们可以 用如下内容代替图 2. 6 的 1) 行:interface Movie( key (title, year) ){即使只说明一个键码, 我们也可以用 keys 代替 key。类似地, 如果名字是类Star的键码, 那么我们可以在图 2. 6 的 8) 行花括号之前增加( key name) □属性的几个集合有可能均为键码。如果是这样, 那么可以在字key(s) 之后, 放几个用逗号分开的键码。和通常一样, 包含多个属性的键码必须用括号把其属性表括起来, 这样我们就可以把几个属性构成的一个键码和几个单一属性的键码区别开了。例 2. 26 作为有多个键 码的例子, 考虑一下类 Employee, 我们 不在这里描述属 性和联系的完整集合。然而, 假定它的两个属性是empID( 雇员号) 和ssNo( 社会保险号) 。那么, 我们可以用(key empID,ssNo)说明这些属性中的每一个均为键码。因为属性表没用括 号括起来, ODL 把上述说明 解释为 两个 属性 中的每 一个 均为 键码。如 果我 们用 括号 把列 表 (empID,ssNo) 括 起来, 那 么·64·
ODL 将解释成两个属性一起构成一个键码。也就是说, 如下写法(key(empID,ssNo) )的含意是不会有两个雇员既有相同的雇员号, 又有相同的社会保险号, 不过两个雇员可能在这两个属性之一取值一致。 □2. 5. 3 在 E/ R 模型中 表示键码实体集在本质上就是类, 它可以有和 ODL 的类意义上完全相同的键码。如果一个属性集构成一个实体集的键码, 那么在该实体集中将不会有这样的两个实体, 它们的键码的每 个 属 性 值 都 一 致。在 E/ R 图表示法中, 我 们 在属 于 实体 集的 键码 属性 下 划线, 例如,图 2. 24 标出键码的电影实体集图 2. 24 表 示 图 2. 8 的 实体 集Movies, 属 性title和year一起作为键码。在有多个键码的情况下, 在E/R模 型中, 不 提供正式的表示 法来表 明所有的 键码。通常 把一个 键码 称为 主键码, 并把该属性集看作好像是实体集唯一的键码。而在E/R 模 型中, 主键码 用下划 线标 明, 其他键 码, 称为 次键码,将不予标明或者附在图旁的注释中。一个不常见但是可能的情况是, 一个实体集的键码不属于实体集本身。我们将推迟到 2. 6 节考虑这种称为“弱实体集”的情况。2. 5. 4 单值约 束通常, 数据库设计的一个重要特性是最多有一个值扮演特定角色。例如, 我们假定一个电影对象有唯一的名称、年份、长度和电影类型, 并且一部电影属于唯一的制片公司。用ODL 说明这些假定并不困难, 因为每个属性有一个类型。如果类型不是一个聚集类型( 例如, 集合) , 那么可能对于该属性只有一个值或者对于一个联系只有一个相关的对象。另一方面, 如果把属性或 联系定义 为聚集 类型, 例如图 2. 6 中 6) 行 的联系 stars 的 类型为 Set〈St ar〉, 那么就允许一部给定的电影和多个影星相关。这样的联系称为多值联系。我们还必须区 别一个属性或联 系最多有 一个值的 情况和必 须正好 有一个值 的情况。当一个联系把一个类中的对象和另一个类中的单一对象相连, 并要求后一个对象存在时,则存在一个称为“参照完整性”的约束, 我们将在下面的 2. 5. 5 节讨论。当一个属性为单值时, 我们有两种选择:1. 可以要求该属性值存在。2. 可以允许该属性值任选。如果一个属性 构成一个类的部 分键码, 那么我 们一般要 求每个 对象中都 存在该属 性值。对于其 他属性, 当 属性值不存在时, 我们可 以对该属性建立“null”值, 来代 替实际值。那么, 该属性的非空值将是任选的。例 2. 27 对于类Movie, 我们在例 2. 23 中已确定它的键码是title和year。我们要求这两个属性在所有电影对象中都存在。另一方面, 属性 lengt h 可以有选择地省略。例如,我们可以用- 1 作为length的空值, 因为电影的长度不会为负。如果我们不知道电影的长·74·
度, 我们将把属性 lengt h 的值设置为- 1。类似地, 我们为定义属性 filmType 的各种可能值而增加第三个枚举值。除了值color和blackAndWhite, 我们可以选择一个值例如Null或 Unknown 以表明没有关于电影类型的信息。 □实体/ 联系模型也提供了表示单值约束的方法。实体集的每个属性都隐含地有单一的值。通常我们假定, 一个值可能为空。不能为空的属性将在边上标明。表明多对一或一对一联系的箭头也表示单值约束。也就是说, 如果一个联系有指向实体集 E 的箭头, 那么最多有实体集 E 中的一个实体与从其他相关的 每个实体集中选 择的一个实体相连。2. 5. 5 参照完 整性单值约束断言 对一个给定的角 色最多有 一个值存 在, 而 参照完 整性约束 断言对于 这个角色正好有一个值存在。我们看到的约束是, 属性有非空的单值, 把这种约束作为参照完整性的一种要求, 但是“参照完整性”更多地用于引用类间的联系。让我们考虑图 2. 6 的ODL说明中 7) 行从Movie到Studio的联系ownedBy。人们可能会问, 有一个制片公司对象作为 ownedBy 的值, 而那个 制片公司对象却 不存在, 这 怎么可能呢? 答案是, 在ODL的实现中, 联系ownedBy将通过指针或对制片公司对象的 引用来表示, 而且 有时可 能从类 Studio 中 删除制 片公 司对象。 在这种 情况 下, 指针成 为悬 挂(dangling) 状态; 它将不再指向实际的对象。对联系 ownedBy 的参照完整性约束将要求所引用的制片公司对象必须存在。有几种方式来实施这种约束。我们可以禁止删除要引用的对象( 如例子中的制片公司) 。我们可以要求如果删除要引用的对象, 那么也要删除引用它的所有对象。在我们的例子中, 这种方法要求, 如果删除一 个制片公司, 那么 也要从数 据库中 删除所有 属于该制 片公司的电影。除了关于删除 的这两个原则之 外, 我 们还要 求, 当 建立电影 对象时, 把一 个存在的 制片公司对象给该电影对象作为它的联系ownedBy的值。 进而, 如果 该联系的值发生 了变化, 那么新值也必须是一个存在的对象。实施这些原则以保证联系的参照完整性是数据库实现的一个内容, 而在这里我们不讨论具体细节。2. 5. 6 E/ R 图中的参照 完整性我们可以扩展 E/ R 图中的箭头表示法, 来表示是否要求联系在 一个或多个方向 上支持参照完整性。假定R是一个从实体集E到实体集F的联系。我们将用一个指向F的圆箭头来 表明不仅 联系 R 是由 E 到 F 的多对 一或一 对一的联 系, 而 且对于 实体集 E 的 一个给定实体要求, 存在与之相关的实体集F的实体。当R是在多个 实体集之间的联 系时这个概念也同样有效。例 2. 28 图 2. 25 表示实体集Movies,Studios和Presidents之间的某些恰当的参照完整性约束。这些实体集和联系最初在图 2. 8 和 2. 9 中介绍了。我们看到一个圆箭头从联系Owns进入Studios。该箭头所表示的参照完整性约 束就是, 拥 有一部电影的制 片公·84·
司必须总是存在于 Studios 实体集中。类似地, 我们看到一个圆箭头从Runs进入Studios中。该箭头 表示的参照完整 性约束就是, 如果一个总裁经营一个制片公司, 那么该制片公司存在于 Studios 实体集中。图 2. 25 表示参照完整性约束的E/R图注意, 从 Runs 至 Presidents 的箭头仍为尖箭头。这种选择反映了制片公司及其总裁之间合 理的假设。如果一个 制片公司不再存 在, 那 么它的总 裁也不 再称为( 制片 公司) 总裁, 于是我们将期望把该制片公司的 总裁从 P residents 实体集 中删除。因此指向 Studios的是圆箭头。另一方面, 如果把一个总裁从数据库中删除掉, 制片公司将继续存在。于是,我们用普通的 尖箭头指向 President s, 表明每 个制片公司最多 有一个总裁, 但有时可能 没有总裁。 □2. 5. 7 其他类 型的约束正如本节开始提到的那样, 人们可能还希望在数据库中实施其他类型的约束。在这里我们将对其他约束作简短的说明, 在第 6 章将对这个课题作详细阐述。域约束把属性的值限制在一个有限的集合内。ODL 需要每个属性的 类型, 而这 个类型就是域约束的初级形式。例如, 如果属性长度是整型, 那么长度的值就不能是 101. 5 或任何其他的非整数。然而, ODL 不支持更进 一步的限制性约束, 例如长 度在 60 和 240 之间。我们将在 6. 3 节看到SQL支持这种约束。还有更多普 通类型的约束不 属于本节 提到的任 何一个 范畴。例如, 关于 联系度的 约束, 比如一个电影对象或实体不能通过联系stars与十个以上的影星对象或实体相连。在E / R 模型中, 我们 可以把 一个 极限数 附在 联系 和实体 集的 连线旁 边, 以表 明限制 与有 关实体集的任何一个实体相连的实体数。在ODL中, 我们可以让Movie的联系stars为长图 2. 26 表示每部电影影星数的约束度等于 10 的数组类型, 从而限制影星的数量。然而, 没有方法规定一个集合最多有十个元素。例 2. 29 图 2. 26 表 明 在 E/ R 模 型 中如 何表 示 这 样 的 约 束: 没 有 一 部 电 影 的 影 星 数 大 于10。作为另一个例子, 我们可 以把箭头看作约束“≤1”的同义词, 并且我们可以 把图 2. 25中的圆箭头看作表示约束“= 1”。 □2. 5. 8 本节练 习练习 2. 5. 1: 选择并说明你在下列练习中所做的ODL设计的键码:* ( a) 练习 2. 1. 1。( b) 练习 2. 1. 3。* ( c) 练习 2. 1. 5。(d) 练习 2. 4. 1。·94·
练习 2. 5. 2: 对于你在下列练习中所做的 E/ R 图:* (a) 练习 2. 2. 1。( b) 练习 2. 2. 3。(c) 练习 2. 2. 6。( d) 练习 2. 4. 3。(i) 选择并说明键码; (ii) 指出适当的参照完整性约束。! 练习 2. 5. 3: 我们可以把 E/ R 模型中的联系想象成具有键码, 正如实体集那样。令 R 为实体集E1 ,E2 , …,En 之间的 联系, 则R的 键码是选 自E1 ,E2 , …,En 的属 性集K, 从而 如果( e1 , e2 , …, en ) 和( f 1 , f 2 , …, f n ) 是 R 的联系集中的两个 不同的元组, 那么这 两个元组 不可能在K的所有属性上都一致。现在, 假定n= 2; 也就是说,R是一个二元联系。另外, 对于每个 i, 令 Ki 是作为实体集 Ei 的键码的属性集。按如下假设, 利用 E 1 和 E2 , 给出 R 可能的最小键码:( a) R 是多对多联系。* (b)R是从E1 到E2 的多对一联系。( c) R 是从 E 2 到 E 1 的多对一联系。(d)R是一对一联系。! ! 练习 2. 5. 4: 再考虑练习 2. 5. 3 的问题, 但是 n 可以为任 何数, 不只 是 2。仅用从 R 到Ei 的哪条弧线有箭头这一信息, 说明如何利用Ki 找出R可能的最小键码K的方法。! 练习 2. 5. 5: 从 现实生 活中 给出建 立属 性的 主要目 的是 作为键 码的 其他 例子( 例 2. 24除外)2. 6 弱 实 体 集有一个奇特而 又似乎合理的情 况, 即组 成一个 实体集键 码的属 性中的一 些或全部 属于另一个实体集。这样的实体集称为弱实体集(weak entit y set) 。2. 6. 1 产生弱 实体集的 原因有两个主要的弱实体集的来源。第一, 有时实体集属于一种层次结构。如果实体集 E的实体是实体集F的实体中的子单元, 那么只有把E的实体所从属的F的实体名考虑在内, E 的实体名才是唯一的。例 2. 30 引入弱实体集的层次结构的某些实例如下:1. 一个电影制片公司可能有几个电影组( Crew) , 电影组可能由给定的制片公司命名为 1 组、2 组, 等等。然而, 其他制片公司可能对电影组使用相同的名字, 所以属性number( 组号) 并不是电影组的键码。相反, 为了唯一地命名一个电影组, 我们需要同时给出它所属的制片公司的名字和组号, 如图 2. 27 所示。弱实体集Crew的键码是它自己的number属性和通过多对一的联系 unit-of 与电影组相关的唯一制片公司的 name 属性。①·05·①双 边菱形 和双 边矩形 将在 2. 6. 3 节 予以 说明。
为什么在ODL中没有“弱类”?在ODL或任 何面向 对象 的模型 中从 未出 现如何 找出 键码 的问 题。正 如我 们在2. 5. 2节看到的, 我们可以说明由一个或几个属性构成键码, 但是我们不必这样做。对象有“对象标识”(object ident ity) , 实际上 是可以找到对象的 地址, 并 且对象标 识能唯一地区别对 象, 即使它 们的属性值或联 系不能区别两个 对象。另一 方面, E/ R 模型是“面向值”的, 实体仅仅通过它们的相关属性值相区 别。因此, 在E/R设 计中我们必须注意, 任何实体集的实体只用值就可以互相区别, 而不求助于任何“对象标识”。2. 物种通过它的属名和种名来命名。例如, 人属于人类(Homo Sapiens) 种;Homo是属名, Sapiens 是种名。通常, 一个属包括几个物种, 每个物种的名字开头是属名, 随后是种名。令人遗憾的是, 种名不是唯一的。两个或多个属可能有种名相同的物种。于是, 为了唯一 地命名 一个 物种, 我们 同时 需要种 名和 与该 物种相 关的 属名, 而该 物种 则通 过联 系Member-of与它的属相连。物种是一种弱实体集, 它的键码部分来自包括它的属。 □图 2. 27 弱实体集及其连接弱实体集的第 二个常见的来源 是连接实 体集, 我们已在 2. 2. 5 节作为消 除多向联 系的方法介绍过了。①这类实体集往往没有它们自己的属性。它们的键码由它们所连接的实体集的键码属性构成。图 2. 28 连接实体集是弱实体集·15·①注 意, 在E/R模型中 没有 消除多 向联系 的要 求, 不过 对于O DL以 及某 些较 旧的 模型 , 例 如, 在 2. 7 节讨 论的网状模 型和 层次模 型, 我们 必须替 代多 向联系 。
例 2. 31 在图 2. 28 我们看到代替例 2. 8 的三元联系 Contracts( 签约) 的连接实体集Contracts。Contracts有一个属性salary( 酬金) , 但是这个属性不属于键码。相反地, 签约的键码包括制片公司的名字和所涉及的影星, 加上所涉及的电影名称和年份。 □2. 6. 2 对弱实 体集的要 求我们不能不加选择地得到弱实体集的键码属性。相反, 如果E是一个弱实体集, 那么提供一个或多个 E 的键码属性的每个实体集 F 必须通过联系 R 和 E 相连。此外, 还必须满足下列条件:1. R 必须是从 E 到 F 的二元的多对一的联系。①2.F为E的键码提供的属性必须是F的键码属性。3. 然而, 如果 F 本身是弱实体集, 那么 F 提供给 E 的键码属性可能是通过多对 一的联系与F相关的某个实体集的属性。4. 如果有几 个从 E 到 F 的多对 一联系, 那么 每个联系 都可能 用于提供 F 的键码 属性的一个副本来帮助构成E的键码 属性。注意,E的实体e可能通 过来自E的不同 联系和 F 的不同实 体相关。这样, F 的几个不 同实体 的键码可 能出现在 标识 E 的特定实 体 e的键码值中。为什 么需 要这 些条件, 直 觉的 原因如 下。考虑 一下 弱实 体集 中的 一个 实 体, 比如 例2. 30中的Crew( 电影组) 。理论上, 每个组是唯一的。即使两个组有相同的序号, 但由于属于不同的制片公司, 原则上我们可以把它们区别开。只依靠有关电影组的数据来区别各个组是很困难的, 因为只有组号还不够。我们可以将附加信息和组相连接的唯一方法是, 假定有某种决定性过程, 它所产生的附加值使组的命名唯一。找到和一个组实体相连的唯一值的唯一方法是, 假设或者1. 该值是 Crew 实体集的属性, 或者2. 我们可以采 用从一个Crew实 体到某个其他实 体集的唯 一实体 的联系, 并且该 其他实体有某种类型 的唯一相关值。也 就是说, 所 采用的联系必须 是到另一个实体 集 F 的多对一( 或一对一, 作为特例) 联系, 并且该相关值必须是F的键码。2. 6. 3 弱实体 集的表示 法我们应当采用下列惯例指明实体集是弱的并说明它的键码属性。1. 如果一个实体集是弱的, 将用双边矩形表示。图 2. 27 中的Crews和图 2. 28 中的Contracts 就是这个惯例的实例。2. 如果一个实体集是弱的, 那么连接它将和 提供其键码属性的 其他实体集的多 对一联系将用双 边菱形表示。图 2. 27 中的 Unit-of 和图 2. 28 中的所有三 个联系都是这个 惯例的实例。3. 如果一个实体集 为 它本 身 的键 码 提 供任 何 属性, 那么 这 些属 性 将加 下 划线。 图2. 27给出一个实例,Crew的序号参与了它本身的键码, 不过它不是Crew的全部键码。·25·①记 住, 一对一 联系 是多对 一联 系的一 个特例 。当 我们说 联系 必须是 多对 一时, 也总 是包 括一对 一联 系。
我们可以将惯例概括如下:当看到一个 实体集有双边时, 它就 是弱的。它 的键码包 括其属 性中带下 划线的部 分( 如果有的话) , 及通过具有双边的多对一联系与弱实体集相连的实体集的键码属性。2. 6. 4 本节练 习* 练 习 2. 6. 1: 一种表 示学生及 其各门课 成绩的 方法 是使 用与学 生、课程 以及“注 册”相对应的实体集。“注册”实体构成学生和课程之间的“连接”实体集, 它不仅可用于表示一个学生修了某 门课程这一事实, 而且可用 于表示该 学生该 课的成绩。 针对这种 情况画一 个E/R图, 表示出弱实体集以及实体集的键码。成绩是实体集“注册”键码的一部分吗?练习 2. 6. 2: 修改练习 2. 6. 1 以便记录学生一门课中几次作业的成绩。再次指出弱实体集和键码。练习 2. 6. 3: 指出你在练习 2. 3. 3 设计的 E/ R 图中的弱实体集和键码。练习 2. 6. 4: 为涉及弱实体集的下列情况画出E/R图。在每种情况下指出实体集的键码。 (a) 实体集Courses( 课程) 和Departments( 系) , 一门课由唯一的 系提供, 而 它仅有的属性是课程号。不同的系可以提供具有相同号码的课程。每个系都有唯一的系名。* ! (b) 实体集Leagues( 社团) 、T eams( 球队) 和Players( 队员) 。社团的名字是唯一的。一个社团不会有两个同名的球队。一个球队不会有两个同号的队员。然而,在不同的球队可以有同号的队员, 并且在不同的社团可以有同名的球队。2. 7 历史上有影响的模型在这一节我们将向读者介绍另外两种模型和它们的一些术语。“网状”和“层次”两种模型是早期为数据库系统提供基础所做的尝试。它们用于第一批商业数据库系统, 开始日期为 20 世纪 60 年代后期和 70 年代。它们由基于关系模型的系统取而代之, 关系模型将是第 3 章的主题。然而, 它们的几个概念都注入到更新的面向对象的数据库设计方法中。2. 7. 1 网状模 型我们可以把网状模型看成只限于二元的多对一联系的E/R模型。网状模型的两个主要元素是:1. 逻辑记录类型。与实体集类似; 它们包括类型名和属性表。逻辑记录类型的成员称为记录; 它们与 E/ R 模型中的实体类似。2. 链 接。是多对一的二元 联系。它们连接两个 实体集; 其 中一个是owner( 系主) 类型, 另一个是member( 成员) 类型。链接是从成员类型到系主类型的多对一联系。也就是说, 成员类型的每个记录都正好分配给系主类型的记录, 而系主类型的每个记录可“拥有”0, 1 或多个成员类型的记录。例 2. 32 让我们用网状模型实现关于电影、影星和他们主演 的电影的实例。每 个影星和电影都构成一个逻辑记录类 型。然而, 在 电影和影星之间 的“主 演”(“stars-in”) 联系是多对多联系, 所以我们不能在网状模型中用单个的链接表示这种联系。相反, 我们必须·35·
建立一个新的逻辑记录类型, 我们将称其为 StarsIn( 主演) , 作为“连 接”逻 辑记录类型, 与我们在 2. 25 节介绍 的连接 实体集类 似。把每个St arsIn记录 看成是代 表一个影 星-电 影对, 于是影星就出现在电影中。因此按网状模型设计的三个逻辑记录类型为:Stars( name, address)Movies(tit le,year,length,filmT ype)StarsIn( )Stars( 影星) 逻辑记录类型 有两个属 性: 影 星的姓名 和地址, 而 Movies( 电影) 类型 的属性有关于电影的名称、年份、长度和电影类型等。我们并未表示和电影有关的制片公司,不过一个完 整的设计应通过 一个链接表示它 们之间相连。连接 类型St arsIn在我们的 设计中没有属性, 不过如果合适, 可以给予属性。例如, 如果我们要记录演一部特定的电影给影星酬金的数量, 那么这个数量就是电影-影星对的函数, 并且是St arsIn的属性。然而, 在这个例子中,St arsIn记录的效果仅仅通过它所参与的链接体现出来。在我们的设计中有两个链接。一个链接是从 Stars 到 StarsIn; 也就是说, Stars 是系主类型而St arsIn是成员类型。这个链接, 我们称之为TheStar, 连接一个影星和该影星参与的各个电影-影星对。第二个链接是T heMovie, 有系主类 型Movies和成 员类型StarsIn。每个电影记录都拥有包括该电影在内的电影-影星对。注意, 两个链接都是多对一的。电影m的Movies记录和影星s的Stars记录都拥有一个St arsIn对(m,s) 。图 2. 29 说明了三个逻辑记录类型的记录如何通过链接连在一起。这个图不是模式。相反, 它表明个别的记录本身, 并表明通过链接把它们与其他记录相连的方式。我们看到三个StarsIn记录。1 号表示Sharon Stone是电影Basic Instinct的影星这一事实。2 号表示Sharon Stone/T otal Recall影星/ 电 影对, 而 3 号表 示Arnold Schwarzenegger/T otalRecall 影星/ 电影 对。这些 序号 实 际上 并不 是这 些记 录 的一 部分, 而 是使 我 们便 于引 用StarsIn记录。注意,StarsIn记录在两个链接中都是成员, 而其他记录均为系主。 □图 2. 29 记录及其链接2. 7. 2 网状模 式的表示在表示逻辑记录类型和链接的图中, 我们一般用椭圆表示逻辑记录类型, 用命名的箭头表示链接。箭头从成员类型指向系主类型。例 2. 33 例 2. 32 中的三个逻辑记录类型和两个链接的模式图在图 2. 30 中给出。 □·45·
3. 7. 1 异常 96……………………………………………………………………………………3. 7. 2 关系分解 96………………………………………………………………………………3. 7. 3 BC范式 98………………………………………………………………………………3. 7. 4 分解成 BCNF 99…………………………………………………………………………3. 7. 5 函数依赖的投影 102……………………………………………………………………3. 7. 6 从分解中恢复信息 103…………………………………………………………………3. 7. 7 第三范式 105……………………………………………………………………………3. 7. 8 本节练习 107……………………………………………………………………………3. 8 多值依赖 108………………………………………………………………………………………3. 8. 1 属性的独立性及其带来的冗余 108……………………………………………………3. 8. 2 多值依赖的定义 109……………………………………………………………………3. 8. 3 多值依赖的推论 111……………………………………………………………………3. 8. 4 第四范式 112……………………………………………………………………………3. 8. 5 分解成第四范式 113……………………………………………………………………3. 8. 6 范式间的联系 114………………………………………………………………………3. 8. 7 本节练习 114……………………………………………………………………………3. 9 数据库模式实例 116………………………………………………………………………………3. 10 本章总结 118……………………………………………………………………………………3. 11 本章参考文献 119………………………………………………………………………………第 4 章 关系模型中的运算 121…………………………………………………………………………4. 1 关系代数 121………………………………………………………………………………………4. 1. 1 关系的集合运算 122……………………………………………………………………4. 1. 2 投影 123…………………………………………………………………………………4. 1. 3 选择 124…………………………………………………………………………………4. 1. 4 笛卡尔积 124……………………………………………………………………………4. 1. 5 自然连接 125……………………………………………………………………………4. 1. 6 θ连接 127…………………………………………………………………………………4. 1. 7 查询中的复合运算 128…………………………………………………………………4. 1. 8 改名 129…………………………………………………………………………………4. 1. 9 基本和导出运算 130……………………………………………………………………4. 1. 10 本节练习 131……………………………………………………………………………4. 2 关系的逻辑 136……………………………………………………………………………………4. 2. 1 谓词和原子 136…………………………………………………………………………4. 2. 2 算术原子 137……………………………………………………………………………4. 2. 3 Datalog规则和查询 137…………………………………………………………………4. 2. 4 Datalog 规则的含义 138…………………………………………………………………4. 2. 5 外延和内涵谓词 140……………………………………………………………………4. 2. 6 本节练习 140……………………………………………………………………………4. 3 从关系代数到 Datalog 140………………………………………………………………………4. 3. 1 交集 141…………………………………………………………………………………4. 3. 2 并集 141…………………………………………………………………………………·Ⅵ·
为什么采用层次模型人们可能会想为什么层次模型的奇怪的要求一度是数据库产业中的重要推动力。最简单的论点就是用层次模型组织数据( 仅当绝对必须时使用虚拟记录类型) , 可以通过把记录和它们的双亲群集成组的方式把数据存储在顺序文件中。这样, 我们可以如图 2. 32 所 示意的那样存储 数据, 即 一个像 Sharon Stone 那样的记录, 其后有它“拥有的”所有记录, 在这种情况下, 虚拟电影指针就出现在该记录之下。在人们倾向于沿树向下查询信息 的假设下, 可能倾向于在 附近的 文件中找 到所需信 息, 如从双亲 记录移向子女记录, 因此就减少了从磁盘上检索所需信息的时间。在这个例子中, 采用层次 模型没有 什么好 处, 因 为沿着用 虚拟记 录所代表 的指针将把我们带到磁盘上存放电影信息的某个随机的位置。然而, 除了多对多联系以外, 由于采用层次体系结构, 在查询执行的效率上, 往往会有显著地提高。图 2. 30 电影实例的网状模式2. 7. 3 层次模 型可以把层次模 型看成是一种受 限制的网 状模型, 在这里 逻辑记 录类型和 链接构成 一个森林( 树的聚集) 。也就是说, 如果我们把每个链接看作是在表明: 系主类型是成员类型的双亲, 那么逻辑记录类型就构成一个森林。这个要求的问题在于, 对于某些网状模式要实现或许是不可能的。例如, 我们从图 2. 30 看到逻辑记录类型 StarsIn 在层次模式中将需要两个双亲,St ars和Movies。因此, 图 2. 30 不是一个森林。回忆 一下, 逻辑 记录 类型 StarsIn 确实 是对 应于影 星和 电影之 间多 对多 联系 的连 接类型。在层次模型中, 我们通过为每个相关类型建立虚拟副本表示多对多联系。我们可以把虚拟类型看作是代表指向实际类型记录的指针。虚拟类型使任何网状模式都能表示为层次模式。例 2. 34 图 2. 31 表示电影-影星实例的层次模式。在森林中有两个简单树。第一个图 2. 31 电影实例的层次模式根 为St ars( 影 星) , 子 女 为 虚 拟 电 影, 而 第 二 个 根 为Movies( 电影) , 子女为虚拟影星。我们 可 以 把图 2. 31 的模 式 所 代表 的 实际 数 据 形象化为图 2. 32 那样。在该模式中, Stars 类型有子女虚拟电影。于是, 类型Stars的每个记录 有类型为虚 拟电影的子女; 虚拟记录用其中有单词 to 的方框表示。例如, 我们看到影 星记录 Sharon Stone 有两个子女; 每个都是指 向 Movies 记录 的指针, 在这种情况下是指向 Basic Instinct 和 T ot al Recall 的记录。要理解影星和电影之间基本的多对多联系, 我们可以从一个影星记录( 例如Sharon Stone) 出发走下去, 从那里 到子·55·
女虚拟电影记录, 再从其中的每一个到相应的实际电影记录。 □图 2. 32 用层次模型表示电影/ 影星的实例2. 7. 4 本节练 习练习 2. 7. 1: 用网状模型对下列练习描述的内容进行设计:(a) 练习 2. 1. 1。(b) 练习 2. 1. 3。( c) 练习 2. 1. 5。( d) 练习 2. 3. 2。练习 2. 7. 2: 用层次模型重复练习 2. 7. 1。* ! 练习 2. 7. 3: 假定一个实体-联系图有n个实体集和m个二元联系。如果我们将这个图转换为一个网状模型设计, 可能需要的最大和最小的链接数是多少? 记住, 每个联系都可以是多对多、多对一或一对一的。! ! 练习 2. 7. 4: 假定一个实体-联系图有 n 个实体集和 m 个二元联系。如果这个图用层次模型给出, 那么请给出需要的最大和最小的虚拟记录类型数。练习 2. 7. 5: 如 果联系 是k元的(k> 2) , 那么 应对你 在练 习 2. 7. 3 和 2. 7. 4 的回 答作 何修改?2. 8 本 章 总 结 设计表示法: 数据库设 计往往使用实体/ 联系模型 或者面向对象的模 型如对象 定义语言( ODL, Object Definit ion Language) 实施。人们的意图是把 E/ R 模型转换为实际的数据库系统模型, 通常是关系模型。ODL 设计可以用同样的方法处理面向对象的数据库系统或者( 几乎) 可以作为对面向对象数据库系统的直接输入。 对 象定 义 语言: 在 这种 语言 中我 们 通过 给 出类 的 属性、联 系 和 方法 描 述对 象 的类。属性通过它们的数据类型来描述。ODL的类型系统包括传统的基本类型, 例·65·
如整型以及具有记录结构、集合、包、列表和数组形式的结 构类型。联系通过 和它们相连的类描述, 并允许是单值或者多值。 实体/ 联系图: 在 E/ R 模型中, 我们描述实体集、它们的属性 以及实体集之间的联系。实体集的成员称为实体。我们分别使用矩形、菱形和椭圆描绘实体、联系和属性。 联系的多重性: 在 ODL 或者 E/ R 中, 通过联系的多重性来区别联系是有用的。二元 联系 可以是 一对 一、多对一 或多 对多的 联系。 在E/R中允 许多 于两个 实体 集( 类) 之间的联系, 但在 ODL 中不允许。 弱实体集: 在E/R模型中产生的一个偶然的情况是弱实体集, 它要求某些相关实体集的属性以识别它自己的实体。用双边的菱形和矩形的特殊表示法来区别弱实体集。 好的设计: 为了有效地设计数据库, 要求我们需要选定的表示法( 例如ODL或E/R) , 使用适当的元素( 例如, 联系、属性) , 如实地表示现实世界, 而且要求避免冗余—— 重复描述同一个事物或者用一种间接的或过于复杂的方式描述某个事物。 子类: ODL 和 E/ R 两者都支持一种方法来描述类或实体集的特殊情况。ODL 有子类和继承, 而E/R使用一个特殊的联系isa属于来表示一 个实体集是另一 个的特殊情况这一事实。 键码:ODL和E/R两者都允 许用属 性集来说 明键码, 这意 味着该 属性集 的值 唯一地定义一个对象或实体。ODL 还有对象标识符的概念, 该标识符是唯一标识对象的值, 然而用户并不能访问它。 网状模型: 这种模型现在很少使用。它与所有联系全都局限于二元和多对一的 E/R图类似。 层次模型: 这种模型现在也很少出现。它和将实体集排成一个森林并且只有从双亲到子女的多对一联系的E/R模型相似。2. 9 本章参考文献[ 3] 是关于实体/ 联系模型的 原始 论文。文 献[ 4] 和 [ 1] 广泛 覆盖 实体-联系设 计的 内容, 也包括某些其他有助于设计的模型。[ 2] 是 定 义ODL的手 册。这 是对 象数 据管 理组 (ODMG,Object Data ManagementGroup) 正 在进行的 工作。该组 织通过 其电子 邮件地 址 info@ odmg. org 和网页: http: / /www.odmg.org提供关于ODL更新资料的电子访问。[ 1] 'Batini,C. ,S.Ceri,and S.B.Navathe,Conceptual Database Design,Benjamin/Cummings,Redwood City, CA, 1992.[ 2] 'Cattell,R.G.G(ed. ) ,The Object Database Standard:ODMG-93Release1. 2,Morgan-Kaufmann,San Francisco, 1996.[ 3] 'Chen, P . P, The entity-relationship model: toward a unified view of data, ACM Tr ans. onDatabase Systems, 1: 1,pp. 9~36, 1976.[ 4] 'El Masri, R. and S. B. Navathe, Fundamentals of Database Systems , Benjamin Cummings,Menlo Park, 1994.·75·
第 3 章 关 系 数 据 模 型 我们在第 2 章讨论过两种数据建模的方法: 面向对 象和实体-联系模型, 它们很有用,适合描述数据结构。但今天的数据库实现通常总是以另一种模型 —— 关系模型为基础。关系模型是非常有用的, 因为它只有单一的数据建模概念: 关系, 以二维表的形式排列数据。我们 将在第 5 章介绍 关系模型 如何支持 称为SQL( 结构化查 询语 言) 的很 高级的 编程 语言。使用SQL, 您可以用简单的程序, 针对关系中存储的数据完成功能强大的操作。反之, 用第 2 章学到的模型之一进行数据库设计, 通常比较容易。所以, 我们的第一个目标是了解如何把ODL或E/R模型描述的数据库设计转换成关系模型。然后, 我们会看到, 关系模型有它自己的设计理论。这种理论通常称为关系的“规范化”, 基本上以“函数依赖”为基础。函数依赖包括并扩展了我们在 2. 5. 1 节提到的“键码”的概念。使用规范化理论, 在设计特定的数据库时, 常常能改进对关系的选择。3. 1 关系模型的基本概念关系模型使我们能以单一的方式来表示数据: 即以称为“关系”的二维表来表示数据。图 3. 1 是一个关系的实例。其关系名是 Movie, 包含了图 2. 4 所示的例 2. 1 中用 ODL 简单定义的 Movie 类的信息, 我们在图 3. 2 中又一 次给出了该类的 ODL 定义。注意, 这并不是 Movie 类的最终定义, 该定义只包含了四个属性 title, year, length 和 filmType。title year length filmT ypeStar Wars 1977 M124 colorMighty Ducks1991 M104 colorWayne' s World 1992 M95 ecolor图 3. 1 关系 Movie 1) interSface Movie{2) attribute string title;3)attribute integer year;4) attribute integer length;5)attribute enum Film(color,blackAndWhite)filmType;};图 3. 2 类Movie的ODL描述·85·
3. 1. 1 属性关系的首行称为“属性”( att ribute) ; 图 3. 1 中, 属性是 title、year、length 和 filmT ype。关系的属性就是关系中各列的名字。通常, 属性描述了所在列各项的含义。例如, 属性为lengt h 的列存放以“分钟”计算的各种电影的长度。注 意, 图 3. 1 中, 关系Movie的属性 与图 3. 2ODL定 义中 称为“属 性”(attribut e) 的结构元素对应。将类的属性作为它所对应的关系的属性, 这种做法是很常见的。但一般而言, 并不要求关系的属性对应于用ODL或E/R描述的数据的任何特定分量。3. 1. 2 模式关系名和关系的属性集称为关系的“模式”。我们用括号把属性集括起来, 并把关系名写在括号的前面来表示关系的模式。于是, 图 3. 1 中的关系Movie的模式就是Movie ( title, year, length, filmT ype)请记住: 关系模式中的属性是一个集合, 而不是列表, 为了便于讨论, 我们通常需要为属性规定“标准”顺序。这样, 无论何时, 要引用具有一组属性的关系模式, 不管是要显示该关系还是显示它的部分行, 都使用这个标准顺序。在关系模型中, 数据库设计包含一个或多个关系模式。所设计的关系模式的集合称为“关系数据库模式”, 或简称为“数据库模式”。3. 1. 3 元组除了由属性组成的标题栏以外, 关系的各行称为“元组”。元组的各分量分别对应于关系的各个属性。例如, 图 3. 1 中的三个元组中的第一个, 有四个分量Star Wars、1977、124和 color, 分别对应于四个属性 tit le、year 、length 和 filmT ype。当我们希望单独地、而不是作为关系的一部分写出一个元组时, 一般用逗号把各分量分开, 并用括号将整个元组括起来。例如,(St ar Wars, 1977, 124,color)就是图 3. 1 中的第一个元组。注意, 一个元组单独出现时, 属性并不伴随元组出现, 因此,必须以某种方式指明该元组所属的关系, 并且始终应该使用关系模式中列出的属性顺序。通常, 可 以认 为元组 表示 对象, 而元 组所 属的 关系则 表示 对 象的 类。上 例中 的关 系Movie的确就是这种情形: 每个元组表示一个电影对象。这些元组的分量和图 3. 2 中描述的电影对象的特性是等同的。但我们应该意识到对象有同一性, 而元组没有。也就是说,原则上, 电影用面向对象 表示法可能出现 两个不同 的电影对 象的所 有属性值 完全相同 的情况, 尽管我们在例 2. 23 中已经讨论过, 对于电影, 我们并不希望出现这种情况。而关系是元组的集合, 在给定的关系中一个元组不可能出现一次以上。因此, 如果用关系来表示一类对象, 我们必须保证该关系有足够的属性集, 从而使任何两个对象的各属性值不会完全相同。对于电影, 我们在例 2. 23 中要求两部电影的名称和年份不能都相同。然而, 在最坏的情况下, 我们也许需要建立一个属性作为对象的人为标识。例如, 我们可以给每部电影唯一的整数“电影标识”(movie ID) , 并将movieID加入到关系Movie的属性集中。·95·
元组的形式表示法虽然我们在本 书中将元组表 示成列表, 其 中每个关 系的属性 顺序是 已知的, 但是元组还有一种允许我们回避固定属性顺序的形式表示法。可以认为元组是一个函数,该函数反映了 从关系模式的各 个属性到该元 组对应 于这些属 性的各 个分量值 的对应关系。比如, 例 3. 1 中用两种方法表示的元组可以看作如下函数:tit le→St ar Warsyear → 1977length→ 124fileT ype → color3. 1. 4 域关系模型要求每个元组的每个分量都是原子的, 即必须属于某种基本类型, 如整型或字符串型。不允许一个值为记录结构、集合、列表、数组或者能合理地分解为更小分量的其他任何类型。这 种要求是造成 ODL 中 的属性不能直接 转换成关系 中的单个 属性的原 因之一。例如, 如果某ODL属性name的类型为Struct Name{st ring first,st ring last}那么, 对应的关系中必须用两个属性 first 和 last 与之对应。我们将 在 3. 2. 2 节更详 细地讨论这个问题。此外, 假设与关系的每个属性相关的特定的基本类型称为“域”, 那么关系的任何元组的每个分量都必须在对应列的域中取值。例如, 图 3. 1 中, 关系Movie的元组的第一个分量 必 须 是 字 符 串, 第 二 个 和 第 三 个 分 量 必 须 是 整 数, 第 四 个 分 量 必 须 在 常 量color和blackAndWhite 中取一个。3. 1. 5 关系的 等价表示 法我们已经学过, 关系的模式和元组都是集合, 不是列表。因此, 它们的顺序是无关紧要的。例如, 我们可以将图 3. 1 中的三个元组以它们的六种可能顺序的任一种排列, 而得到的关系都与图 3. 1 中的一样。而且, 我们可以按选定的顺序对关系的属性重新排列, 而关系并不改变。然而, 当我们对关系模式 重新排序时, 我们必须用 心, 记 住属性是 列的标 题。因此, 当改变 属性的顺 序时, 也应该改变列的顺序。当列移动的时候, 元组的分量也改变了顺序。结果是每个元组分量的排列都与属性的排列一致。例如, 图 3. 3 给出了通过改变行和列的顺序从图 3. 1 可能得到的许多关系中的一个。这两个关系可以看作是“相同的”。更确切地说, 这两张表是同一关系的不同表示法。注意, 重排属性和列以一种独立的形式对元组产生影响。例 3. 1 图 3. 1 中的元组(St ar Wars, 1977, 124,color)·06·
模式和实例我们不要忘记关系模式和关系实例之间的重要区别。模式是关系名和关系的属性集, 相对比较稳定; 而实例是关系中元组的集合, 可能在频繁地变化。模式/ 实例的区别在数据建模中是常见的。例如, 在第 2 章中, 我们介绍了定义类的结构的 ODL 接 口定义和该类中 的对象集之间的 区别。接口定 义类似 于模式, 而它所定义的类的对象集则为实例。类似地, 对实体集和联系的描述是描述模式的E/R模型方法, 而实体集和联系集则构成 E/ R 模式的实例。然而, 请记住, 设计数据库 时, 数据库实例并不是设计的组成部分。当我们进行设计时, 只是想象典型的实例可能是什么样子的。和图 3. 3 中的元组( 1977, Star Wars, color, 124)表示同一个对象。然而, 我们只有知道相应关系的属性顺序, 才能确定它们的等价性。因此, 为关系选择一个属性顺序并在对关系进行操作的时间内保持该顺序, 这种做法通常是明智的。 □year title filmType length1992 Wayne's Wor ld color 95 61991 Mighty Ducks color104 d1977 Star Wars color124 d图 3. 3 关系 Movie 的另一种表示3. 1. 6 关系实 例与电影有关的关系不是静止的, 而是随时间变化的。我们希望这些变化反映在关系的元组中。例如, 当数据库要 增加电影时, 就插 入新的元 组; 当 得到电 影的修订 或更正信 息时, 就修改现有的元组; 也可能由于某种原因, 有的电影要从数据库中清除掉, 这时就要删除相应的元组。关系模式的变化就不那么常见了, 但我们想增加或删除属性的情形也是有的。模式的变化, 可能会发生在商用的数据库系统中, 但其代价是很昂贵的。因为, 可能要改写上百万元组中的每一个—— 增加或删除一些分量。如果我们增加一个属性, 要找到元组的新分量的正确取值是困难的, 甚至是不可能的。我们将给定关系中元组的集合称为该关系的“实例”。例如, 图 3. 1 中的三个元组就构成了关系 Movie 的一个实例。可以假定, 关系 Movie 已经随时间发生了变化, 并将继续随时间变化下去。比如, 在 1980 年,Movie中并不包括Mighty Ducks或Wayne' s World元组。然而, 传统的数据库系统只维护任何关系的一个版本:“现在”该关系中元组的集合。关系的这个实例称为“当前实例”。·16·
3. 1. 7 本节练 习练习 3. 1. 1: 图 3. 4 中是两个关系实例, 它们可能是某银行数据库的一部分。请写出:(a) 每个关系的属性;( b) 每个关系的元组;(c) 从每个关系选一个元组, 写出它的分量;( d) 每个关系的关系模式;(e) 数据库模式;( f) 每个属性相适应的域;(g) 每个关系的另一种等价表示法。练习 3. 1. 2: 如 果某关系的实例 满足下列条件之 一, 要表示 该实例, 有 多少种不同的方 法( 考虑元组的顺序和属性的顺序) :* ( a) 3 个属性, 3 个元组, 如同图 3. 4 中的关系 Accounts;(b) 4 个属性, 5 个元组;( c) n 个属性, m 个元组。accNo type balance12345 savings 12000 G23456 checking1000 34567 savings25 关系AccountsfirstName lastName idNo accountRobbie Banks 901 -222 12345 Lena Hand805 -333 23456 Lena Hand805 -333 34567 关系Customers图 3. 4 银行数据库的两个关系3. 2 从 ODL 设计到关系设计让我们来考虑新的数据库( 比如我们的电影数据库) 建立的过程。我们从设计阶段开始。设计阶段中, 我们回答下述问题: 存储什么信息, 信息元素如何相互联系, 有哪些假设的约束( 比如键码或参照完整性) 等等。这个阶段可能会延续很长时间, 在此期间, 评估各种方案, 协调不同意见。设计阶段之后, 是利用实际的数据库系统进行实现的阶段。因为大多数商用数据库系统都使用关系模型, 我们就假定设计阶段也是使用这种模型, 而不是用我们在第 2 章讨论的面向对象的ODL模型或E/R模型。然而, 实际上, 首先用第 2 章所讲的模型之一进行设计, 然后转换成关系模型, 这种做法往往更容易。这么做的主要原因是关系模型中只有一个概念—— 关系—— 而没有其他辅助概念( 如 E / R 模型中的实体集 和联 系) , 因 而存在 一些 不灵活 的地 方, 而这些 问题 最好是在设计方案选好之后再进行处理。在本节, 我们将考虑如何把 ODL 设计转换成关系设计。3. 3 节将讨论从 E/ R 模型到关系模型的转换。3. 4 节将研究子类的问题。因为ODL和E/R模型对子类( 属于联系)·26·
枚举和日期的表示ODL有一些原子类型 —— 尤其是枚举和日期 —— 在标准的关系模 型中不能直接表示。但这些类型并不是本质问题。例如, 枚举实际上是前几个整数的别名列表。因此, 对 应于一 周七天的ODL枚举类 型可以 用整型的 属性来表 示, 不过只 使用 0 到 6。另 外, 也 可以用字 符串类 型的 属性来 表示, 用 字符串“Mon”,“T ue”等 来表示 每一 天。同样,ODL中的日期在关系模型中可以用字符串型的属性来表示。当我们在第 5 章讨论关系 查询语言 SQL 时, 将会发 现这种语 言支持 枚举或 日期 类型的 属性, 就 像 ODL一样。的处理有所不同, 所以它们向关系的转换也稍有不同。我们常把约束看作是数据库模式的一部分。ODL或E/R模型中的约束, 比如键码约束和参照完整性约束, 也能在关系模型中表示。关系模型中一类重要的约束, 称为“函数依赖”, 将在 3. 5 节讨论。关于关系的其他类型的约束将在 4. 5 节开始研究。3. 2. 1 从 ODL 属性到关系 属性作为起 点, 假 定我们的 目标是 为每个类 建立一 个对应的 关系, 而类的 每个特 性对 应于该关系的一个属性。我们将会看到这种方法在很多方面必须修改, 但目前, 我们先考虑最简单的可能情况, 即我们确实能把类转换成关系, 把类的特性转换为关系的属性。我们假设的限制条件是:1. 类的所有特性都是属性, 而不是联系或方法;2. 属性的类型是原子的, 而不是结构或集合。例 3. 2 图 3. 2 是满足上述条件的类的 例子。它有四个属性, 除此之 外, 没有别 的特性。每个属性都是原子类型的: title 是字符串, year 和 length 是整数, filmT ype 是二 值的枚举类型。我们用与类名相同的名字建立一个关系, 这里是 Movie。该关系包括 四个属性, 每个对应于类的一个属性。关系的属性名可以和相应类的属性名相同。因此, 该关系的模式和3. 1. 1 节的一样:Movie ( title, year, length, filmType )该类的对象就是在类的四个属性上各取一个值。我们可以用该对象组成一个元组, 只要用每个属性的值作为元组的一个分量即可。我们已经见到过这种转换的结果。图 3. 1 就是一些Movie对象转换成元组的例子。 □3. 2. 2 类中的 非原子属 性很遗憾, 即使一个类的特 性都是属性, 在 类向关系 的转换过 程中, 我们也 还可能遇 到某种困难。原因是ODL中的属性可能有复杂数据类型, 比如结构、集合、多集或列表。反之, 关系模型的一个基本原则是关系的属性具有原子类型, 比如数和字符串。因此, 我们必须找到某种将非原子类型转换成关系的方法。·36·
有关数据质量的注意事项尽管我们努力使实例数据尽可能准确, 但对影星的地址和其他个人信息还是使用了假数据, 以便保护演艺 界成员的隐私 权, 因 为他们 中的许多 人都是 躲避宣传 媒体的害羞的人。记录结构的 域本身都是原子 类型, 所以是最 容易转 换的。我们 简单地扩 展结构的 定义, 使结构的每个域对应于关系的一个属性就可以了。唯一可能有麻烦的是, 两个结构也许有相同的域名。这种情形下, 我们必须在关系中使用新的属性名, 以区别它们。 interface Star{attribute string name;attriZbute Struct Addr{string street,string city}address;}图 3. 5 属性为结构的类例 3. 3 考虑一下例 2. 3 中的类 Star 的初步定义, 图 3. 5 复制了该定义。属性 name 是原子的, 但属性 address 是具有两个域 street 和 city 的结构。因此, 我们用具有三个属性的关系来表示该类。第一个属性name对应于同名的ODL属性。第二和第三个属性, 称为st reet和 city, 分别对应于 adress 结构的两个域, 一起表示一个地址。于是, 我们的关系模式为:St ar ( name, street, city )图 3. 6 中给出了这个关系可能有的一些元组构成的一个实例。 □然而, 记录结构并不是 ODL 类定义中可能出现的最复杂的属性类型。属性值也可以用集合( Set) 、包( Bag) 、数组( Array) 和列表( List) 等类型构造符构造出来。当移植到关系模型时, 每个都会带来它自己的问题。我们将只详细讨论最常见的集合构造符。name street cityCarrie Fisher123 Maple St.HollywoodMark Hamill456 Oak Rd.BrentwoodHarrison Ford 789 Palm Dr . Beverly Hills图 3. 6 表示影星的关系如果属性A是多 个值的集合, 一种表示它 的方法就是针对每 个值建立一个元 组。除了 A 之外, 该元组还包括所有其他属性的合适的取值。首先让我们来看一个例子, 可以看出这种方法很有效, 然后, 我们将会看到这种方法的缺陷。例 3. 4 假设类St ar已有定义, 这样我们就可以为每个影星登记一个地址集。该类的ODL 定义如图 3. 7 所示。下面假设 Carrie Fisher 还有一个海滨住所, 但图 3. 6 提到的另外两个影星只有一个住所。然后, 我们可以建立两个元组, 其名字均为“Carrie Fisher”, 如图 3. 8 所示。其他元组保持与图 3. 6 相同。 □·46·
4. 3. 3 差集 141…………………………………………………………………………………4. 3. 4 投影 142…………………………………………………………………………………4. 3. 5 选择 142…………………………………………………………………………………4. 3. 6 乘积 144…………………………………………………………………………………4. 3. 7 连接 144…………………………………………………………………………………4. 3. 8 用Datalog模拟多重运算 145……………………………………………………………4. 3. 9 本节练习 146……………………………………………………………………………4. 4 Datalog 中的递归编程 147………………………………………………………………………4. 4. 1 固定点运算符 147………………………………………………………………………4. 4. 2 计算最小固定点 148……………………………………………………………………4. 4. 3 Datalog 中的固定点方程 149……………………………………………………………4. 4. 4 递归规则中的求反 153…………………………………………………………………4. 4. 5 本节练习 156……………………………………………………………………………4. 5 对关系的约束 157…………………………………………………………………………………4. 5. 1 用关系代数作为约束语言 158…………………………………………………………4. 5. 2 参照完整性约束 158……………………………………………………………………4. 5. 3 附加约束的例子 159……………………………………………………………………4. 5. 4 本节练习 160……………………………………………………………………………4. 6 包的关系运算 161…………………………………………………………………………………4. 6. 1 为什么用包? 162………………………………………………………………………4. 6. 2 包的并集、交集和差集 163………………………………………………………………4. 6. 3 包的投影 164……………………………………………………………………………4. 6. 4 包的选择 165……………………………………………………………………………4. 6. 5 包的乘积 165……………………………………………………………………………4. 6. 6 包的连接 166……………………………………………………………………………4. 6. 7 包的运算用于Datalog规则 167…………………………………………………………4. 6. 8 本节练习 168……………………………………………………………………………4. 7 关系模型的其他外延 169…………………………………………………………………………4. 7. 1 更新 169…………………………………………………………………………………4. 7. 2 聚合 169…………………………………………………………………………………4. 7. 3 视图 169…………………………………………………………………………………4. 7. 4 空值 170…………………………………………………………………………………4. 8 本章总结 170………………………………………………………………………………………4. 9 本章参考文献 171…………………………………………………………………………………第 5 章 数据库语言SQL172…………………………………………………………………………5. 1 SQL 的简单查询 172………………………………………………………………………………5. 1. 1 SQL的投影 173…………………………………………………………………………5. 1. 2 SQL的选择 175…………………………………………………………………………5. 1. 3 字符串的比较 176………………………………………………………………………5. 1. 4 日期和时间的比较 178…………………………………………………………………5. 1. 5 输出的排序 178…………………………………………………………………………·Ⅶ·
原子值: 缺点还是优点关系模 型看起来 似乎在我 们前进 的路上设 置了障 碍, 因为,ODL更加灵 活, 允许用结构化的值作为特性。人们可能会彻底抛弃关系模型, 或者把它作为一种已经为更高级的“面向对象”方法( 比如ODL) 所取代的早期概念。然而, 事实是基于关系模型的数据库系统在市场上占主导地位。一个原因是关系模型的简单性使得用于查询数据库的精巧而功 能强大的编程语 言成为可能。我 们将在 4. 1 和 4. 2 节介 绍抽象编 程语言—— 关系代数和 Datalog( 数据逻辑) 。也许更重要的是它们在当前的数据库系统中最流行的标准语言SQL中的具体化。 interTface Star{attribute string name;attributeSet<Struct Addr{str ing street,string city}> address;};图 3. 7 有地址集的影星name street cityCarrie Fisher 123 Maple St. HollywoodCarrie Fisher5 8Locust Ln.MalibuMark Hamill 456 Oak Rd. BrentwoodHarrison Ford 789 Palm Dr . Beverly Hills图 3. 8 允许有地址集但是读者应该 认识到, 这 种将集合扩展 为几个 元组的技 术有时 可能会导 致一个糟 糕的关系设计。在 3. 7 节, 我们将考虑可能出现的问题, 也将学习如何重新设计数据库模式。现在, 让我们只考虑一个可能会带来问题的例子。例 3. 5 假设我们 将出生 日期birthdate作 为属性 加到Star类的定义 中; 也 就是说,我们使用图 3. 9 所示的定义。我们在图 3. 7 的 基础上增加了ODL的 原子类型之一Date类型的属性 birt hdate。该属性可以是关系 Star 的一个属性, 这时的关系模式变成:Star(name,street,city,birthdate)让 我 们再 来 修改 图 3. 8 的 数据。 既然 地 址 集可 以 为空, 我们 就 假定 数 据库 中 没 有Harrison Ford 的地址。修订后的关系如图 3. 10 所示。图 3. 10 中, 出现了两个问题。首先,Carrie Fisher的出生日期在相关的元组中重复出现。因此该关系中就有冗余信息。注意, 她的名字也是重复的, 但这种重复不是真的冗余信息, 因为 如果该 名字 不在 各元组 中出 现, 我们就 不知 道两个 地址 都与 Carrie Fisher 相关。区别在于影星的名字是由关系所表示的对象的“键码”, 因此, 应该在表示该影星的每个元组中都出现。相反, 出生日期是有关影星的数据, 而不是所表示的对象的键码, 因此,·56·
它的重复出现是冗余的。 interTface Star{attribute string name;attributeSet<Struct Addr{str ing street,string city}> address;attribute Date birthdate;};图 3. 9 具有地址集和出生日期的影星name street City birthdateCar rie Fisher123 }Maple St.Hollywood9 / 9/ 99Car rie Fisher5 !Locust Ln.Malibu9 / 9/ 99Mark Hamill 456 }Oak Rd. Brentwood 8 / 8/ 88图 3. 10 增加出生日期第二个问题是由于 Harrison F ord 的地址集为空, 我们丢失了关于他的所有信息。特别是, 即使Ford的出生日期出现在与他 对应的Star对象 中, 他的出 生日期也不是关 系的一部分。我们再次提醒读者记住, 这两个问题对于从ODL模式到关系模式的转换方法来说并不是不可避免的。然而, 我们必须意识到这些问题, 并用 3. 7 节描述的“规范化”方法调整关系模式。 □当一个类 的某些属性是聚 集类型( 称 为多值属 性) 时, 表 示单个 对象所需 的元组可 能有多个。我们要为多值属性取值的每种组合建立一个元组。我们将在 3. 2. 5 节从聚集类型的联系的角度讨论这个问题。3. 2. 3 其他类 型构造符 的表示除了记录结构和 集合,ODL类 定义 还可 以用 包(Bag) 、数 组(Array) 或 列 表(List) 来构造各种值。为了表示一个包( 多集) , 一个对象可以是包的 n 次成员, 即包中的元素可以重复出现。我们不能简单地在一个关系中引入 n 个相同的元组,①而是在关系模式中增加一个计数的属性count, 用以表明每个元素是包的多少次成员。例如, 假设图 3. 7 中的地址是一个 包, 而 不 是 集 合。 我 们 可 以 说 123 Maple St. , Hollywood 是 第 二 次 作 为 CarrieFisher的地址, 而 5Locust Ln. ,Malibu是第三次作为她的地址, 表示为:name street city countCar rie Fisher123 Maple St.Hollywood2 Car rie Fisher 5 1Locust Ln . Malibu 3 ·66·①确 切地 讲, 我们不 能在 本章描 述的 抽象关 系模 型的关 系中引 入相 同的元 组。但基 于SQL的关系DBM S却允许有重复 的元 组, 也就是 说, 在SQ L中, 关系 是包 , 而 不是集 合。详细 内容参 阅 4. 6 和 5. 4 节。如果元 组的 数目非 常重要, 即使 DBMS 允许有 重复的 元组 , 我 们还 是建议 您使用 这里 描述的 模式 。
一 个地址 表可 以用 一个 新属 性 —— position( 位置 ) 来表 示, 用来 表明 在 列表 中的 位置。例如, 我们可以将Carrie Fisher的地址表示为一个列表,Hollywood在前, 表示如下:name street city positionCar rie Fisher123 Maple St.Hollywood1 Car rie Fisher 5 1Locust Ln . Malibu 2 最后, 一个定长的地址数组可以用带有数组位置标志的属性表示。例如, 如果地址是两个元素的数组, 每个元素都是 street-city( 街道-城市) 结构, Star 对象的表示如下:name street1 city1 street2 ]city2 PCar rie Fisher123 OMaple St.Hollywood5 }Locust Ln.Malibu3. 2. 4 单值联 系的表示一个ODL类定义 经常包含与其他ODL类的联系。作为例子, 我们来考 虑图 2. 6 中类 Movie 的完整定义, 图 3. 11 复制了该定义。 ?inter face Movie {attribute string title;attribute integer year;attribute integer length;attribute enum Film(color,blackAndWhite)filmType;relationshipSet〈Star〉starsinverse Star∷starredIn;relationship Studio ownedByinverse Studio∷owns;};图 3. 11 Movie类的完整定义让我 们首先 来看 联系 ownedBy, 该 联系 将每部 电影 和制 作它的 制片 公司 联系 起来。我们的第一 印象可能是联系 像是一个属性。与表 示其值为结构 或结构集 的ODL属性 相类似, 我们可以建立关系的属性或属性组来表示相 关类的对象。在联系 为 ownedBy 的情况下, 我们可以针对类Studio的每个特性在Movie的关系模式中增加一个属性。这种做 法的一个 问题是 Studio 对 象有一 个特 性 owns, 它是 指向 类 Movie 的 反向 联系。为了表示这种联系, 每个电影元组将不得不包含它所属的制片公司制作的所有其他电影的信息。原则上, 上述每部电影的信息中都包括它的制片公司的信息, 这样, 又需要包含该 制 片 公 司 制 作 的 所 有 电 影 的 信 息。 显 然, 这 种 解 决 方 案 即 便 可 行, 也 未 免 过 于 复杂了。①·76·①如 果 电影 和 制片 公 司的 系 列不 超 出某 个 制片 公 司的 所 有电 影, 用 类 似于 联 系stars及 其 反 向联 系starr edIn的方法 能让 我们从 一部电 影找 到其中 的所 有影星 , 再 找到 这些影 星主 演的所 有电 影, 以及这 些电 影的所 有影 星, 等 等,很快就 能访 问到数 据库中 几乎 所有的 影星 和电影 。
如果我们想一想实际上对象是如何在计算机主存中存储的, 就能找到更好的方法。当对象O1 包含另一个 对象O2 的引用时, 并不是把O2 复制 到O1 内, 而是O1 中 有一个指 针指向O2 。然而, 关系模型并没有指针或类似于指针的概念。作为替代, 必须用表示相关对象的值来模拟指针的作用。我们所需要的是相关类中构成键码的属性集。如果有了这种属性集, 我 们就 把该 联系 看 成好 像 是相 关 类 的键 码 属性 或 属性 组。下 面 的 实例 将 说明 这 种技术。例 3. 6 假设名字 name 是类 Studio 的键码, 该类的 ODL 定义如下( 见图 2. 6) :interface Studio{attribute string name;attribute string address;relationship Set〈Movie〉owns inverse Movie: : ownedBy;}我们 可以修 改图 3. 1 中 的关 系Movie的关系 模式, 增 加表示 所属 制片 公司的 属性。我 们 可 以 任意 地 将 该 属 性称 为 studioName。在 图 3. 12 中, 我们 可 以 看 到 增 加 的 属 性studioName和一些元组的例子。 □title year length filmType studioNameStar Wars1977 124 color FoxMighty Ducks1991 104 color DisneyWayne's World 1992 95 color Paramount图 3. 12 关系Movie用新属性表示所属制片公司3. 2. 5 多值联 系的表示图 3. 11 中的联系 stars 提出了一个我们在考虑联系 ownedBy 时没有看到的问题。当某联系的类型为一个类时, 我们称该联系为单值的。类型为 Studio 的联系 ownedBy 就是一个单值联系的例子。然而, 当联系是某个类的聚集类型时, 我们称该联系为多值的。例如, stars 就是多值的, 因为它的类型是 Set〈Star〉。换句话说, 从类 A 到类 B 的任何一对多或多对多的联系都是多值联系。为了表示多值联系, 我们需要结合使用下面两种技术:1. 和单值联系的处理方法一样, 必须找出表示每个相关对象的键码;2. 要表 示相关对 象的集 合, 和 值为集合 的属性 的处理方 法一样, 也需 要为每 个值 建立一个元组。也和值为集合的属性一样, 这种方法也带来了冗余。因为对应于集合的每个成员, 该关系的其他属性都将它们的值重复一次。3. 7 节将讨论如何解决这个问题, 目前,我们不妨先接受这种尚不完美的解决方案。例 3. 7 假 设name是 类Star的 键码, 那么, 可以通 过增加属 性starName来 扩展 为·86·
Movie 类设计的关系。该属性将记载电影中出现的某个影星的姓名。这样, 一部电影就可以用许多元组来表示, 元组的数量取决于数据库中列出的影星的数量。图 3. 13 给出了一些实例。请注意数据的冗余; 对应于每部电影的每个影星, 该电影的所有其他信息都将重复一次。 □title year length filmType studioName starNameStar Wars 1977 e124 color Fox Carrie FisherStar Wars1977 e124 color Fox Mark HamillStar Wars1977 e124 color Fox Harrison FordMighty Ducks 1991 e104 color Disney Emilio EstevezWayne's Wor ld1992 e95 }color Paramount Dana CarveyWayne's Wor ld1992 e95 }color Paramount Mike Meyers图 3. 13 含有影星信息的关系Movie有时, 一个类可能有多个多值联系。在这种情况下, 表示单个类对象所需的元组数会爆炸性增长。假设类C有k个多值联系R1 ,R2 , …,Rk , 那么类C对应的关系需要有相应的属性对应于C的所有属性, 并需要有表示C中所有单值联系的键码属性。通常, 还需要有表示 R1 , R2 , …, Rk 的目标类的键码属性。假设 C 的某特定对象 o 通过联系 R1 与 n1 个对象相联, 通过 R2 与 n2 个对象相联, 等等。那么对应于 R1 的对象的每种选择, 对应于 R2 的对象的每种选择, 等等, 我们都需要为对象o建立一个相应的元组。从而在类C对应的关系中, 共有n1×n2× …×nk 个元组对应于对象 o。例 3. 8 假设类C有一组单值属性X和两个多值联系R1 和R2 。假设这两个联系将类C分别与键码属性为集合Y和Z的两个类相联。现在, 考虑类C的一个对象c,c通过联系 R1与键码为 y1, y2的两个对象相联, 通过 R2与键码为 z1, z2, z3的三个对象相联。再假设 x 表示对象 c 中属性集 X 的值。于 是, 对象 c 在 对应 于 类 C 的 关 系 中可 以 用六 个 元组 表 示。我 们 不难 想 象这 些 元组是:( x, y1 , z1 ) ( x, y1 , z2 ) ( x, y1 , z3 )( x, y2 , z1 ) ( x, y2 , z2 ) ( x, y2 , z3 )也就是 Y 对应的键码与 Z 对应的键码的所有可能组合。 □3. 2. 6 假如没 有键码ODL之类的面向对象模型允许一个类的两个对象的所有特性具有完全相同的值。因此, 我们必须对两个影星重名的现象做好思想准备。如果出现重名, 姓名便不再是类Star的键码, 因而, 我们就不能用它表示Movie关系的元组中的影星。也许可以增加一个影星的其他属性作 为键码, 但 原则上, 我们不能保 证下述现 象不会出 现: 两个影星 有相同的 姓名, 同一天出生, 住在相同的地方……在数据库中的其他特性也都相同。·96·
联系的单向表示如果两个实体集之间存在二元联系, 我们就需要在两个实体集对应的关系中选择一个, 并使二者之间的联系出现在该关系中。那么, 究竟选择哪个呢? 如果是多对多或一对一的联系, 选择哪个可能是无关紧要的。但如果是多对一的联系, 我们建议选择包括“多”的联系的实体集( 该实体集中的多个实体对应于另一实体集的一个实体) 。这样做的原因是为了避免冗余。例如, 实体集 Movies 和 St udios 之间的联系 Owns, 最好跟 Movies 放在一起。这样,Movies对应的关系就包含了所属制片公司 名字的属性, 而每个元组 都增加了该制片公司的名字。相反地, 如果我们把该联系加入到 Studios 对应的关系中, 就需要把制片公司的一个元组扩展成多个, 该制片公司拥有的每部电影都对应于一个元组。结果,每个制片公司的所有其他信息在它拥有的每部电影中都要重复一次。唯一可行 的解决方案是建 立一个表示“对象标 识”的新属性, 它 标识的对 象所属的 类与关系相对应。例如, 假设我们不能肯 定 name 是否是影 星的键码, 我们就可以为每 个影星建立一个属性“cert ificate number”( 证书号) , 可能就是他们在演员协会的会员号。证书号 能唯 一 标 识 一 个影 星, 因为, 有 一个 认 证 中 心 负责 处 理 发 放 证 书 并 记 录 已 经 用 过 的号码。例 3. 9 如果为每个影星建立唯一的证书号 并将该证书号作为 联系中表示影星 的键码, 采用这种方法,Movie关系如下所示:title year length filmType studioName cert#Star Wars 1977 124 Gcolor Fox 12345 这里只列出了一个元组, 并假设 12345 是 Carrie Fisher 的证书号。除了 Star 的 ODL 类定义中出现的所有信息以外, 关系St ar还必须包含“证书号 ”这一属 性。下例给出了关 系模式和 Carrie Fisher 的几个元组之一:cert#name street city starredIn12345 Carrie Fisher 123 ^Maple St. Hollywood Star Wars3. 2. 7 联系与 反向联系 的表示一般 来说, 直接 从ODL转 换到 关系 模型时, 我 们将ODL联系 表示 了两 次—— 正 向一次, 反向一次。因此, 在例 3. 7 中, 一部电影中出现的每个影星就放在有该电影名称的一个元组 中; 而 在类Star对应的关 系中, 把联系StarredIn表 示为若 干个 元组, 即对 于每 个影星, 都为他( 她) 主演的每部电影建立一个元组, 记录每部电影的名称和年份( 回忆一下,二者一起构成电影的键码) 。·07·
然而, 同时表示影星联系及其反向联系( 主演的电影) 的做法带来了大量的信息冗余。二者中的任何一个都包含了另一个联系中的全部信息。因此, 作为例子, 完全可以从关系Star 中 省 略 StarredIn 信 息。或 者 反 过 来, 也 可 以 在 Star 关 系 中 保 留 StarredIn, 而 从Movie关系中删除starName属性。3. 3. 2 节将介绍第三种方法—— 将联系和反向联系分离出来, 成为一个新的关系。这种方法有时是( 但不总是) 更可取的。然而, 如果对应于联系的独立关系是最佳选择的话, 3. 7 节介 绍的规范化过程 将会促使我们把 联系分离出来,成为独特的关系, 即使我们进行关系设计的初衷并非如此。注意: 在ODL模型 中, 联系和 反向 联系两 者都 是必 需的, 因为 它们 分别 指的 是从 电影到影星和从影星到电影的指针。指针是不能反向跟踪的, 因此, 两个方向的指针都是需要的。①然而, 在关系模型中, 如同在 E/ R 模型中, 联系是用相关的键码来表示的。可以用包含一对相关键码( 比如电影的名称、年份与影星的姓名) 的元组从两个方向来跟踪联系。3. 2. 8 本节练 习练习 3. 2. 1: 把下列练习中的 ODL 设计转换成关系数据库模式。* (a) 练习 2. 1. 1。(b) 练习 2. 1. 2( 包括原练习规定的所有四种修改形式) 。(c) 练习 2. 1. 3。(d) 练习 2. 1. 4。( e) 练习 2. 1. 5。* (f) 练习 2. 1. 6。练习 3. 2. 2: 把图 2. 7 中的 ODL 描述转换成关系数据库模式。练习 2. 1. 8 中的三种修改对关系模式有何影响?! 练习 3. 2. 3: 图 3. 14 是类“顾客”的 ODL 定义。在该类的对象中, 我们保存了不同类型的 电话( 例 如: 住宅 电话、办公 电话、传 真) 的集合 和“职业介 绍”(referrals) 的集合, 在职 业介绍集中对 该顾客为其他顾 客介绍职业给予 应有的肯定。请把 上述ODL描 述转换成 关系数据库模式。 inter Tface Customer {attribute Struct Name{string first, str ing last } name;attri bute Set<Struct Phone{string type,int number}> phones;relationship Customer referredBy inverse referrals;relationship Set<Customer>referralsinverse referredBy;}图 3. 14 顾客记录·17·①当 然, 不能肯 定给 出的ODL实现将 以预期 的方 式表示“指针”, 因此, 具 体的实 现中这 一对 相反 的联 系可 能只有一种 表示 。
指针: 优点还是缺点?ODL联系 大概是 通过每个 对象指向 一个或 多个相关 对象的 指针 或引用 实现 的。这种实现非常方便, 因为从一个对象出发, 可以迅速找到相关的对象。相比之下, 在关系模 型中, 对象 不是 用指针, 而 是用 键码的 值来 表示的, 要 从一 个对 象找 到 相关 的对象, 似乎搜索得慢一些。例如, 例 3. 7 表 示了 一个Movie对象, 一 部电影 的每 个影 星都有 一个 元 组; 该元组 只 包 含 了该 影 星 的 姓 名, 而 不 是该 影 星 的 所 有信 息。 如 果我 们 想 找 到 Wayne' sWorld中的影星的地 址, 就需要 取出每个影星的 姓名, 并 在St ar关系中查 找该影星对应的元组。然后, 在那里才能找到该影星的地址。也许有人会因此得出结论: 关系模型中没有指针是该模型的一个缺陷。然而, 实际上, 我们 可以为关系建立 索引, 这 样就能很有 效地搜 索在给定 分量上 具有给定 值的元组了( 见 1. 2. 1 节和 5. 7. 7 节) 。所以, 我们在实践中没有使用指针并未损失什么。而且, 虽然指针对于运行于主存、并且最多只需要若干秒的程序非常有用, 但是数据库与这些程序相去甚远。在将会存在若干年、并且可能分布在广泛的分布式计算机系统所连接的许多第二存储设备上的对象之间实现指针, 恐怕要困难得多。所以, 关系模型的无指针方法可能成为一种主流。3. 3 从 E/ R 图到关系的设计E/R图到数据库模式的转换与ODL设计到数据库模式的转换类似。但E/R模型在某种意义上是面向对象的设计和关系设计的中间形式。因此, 从 E/ R 图开始, 有些比较困难的工作我们已经解决了。E/R模型与ODL的主要区别在于:1. E/ R 模型中, 联系作为独立的概念存在, 而不是作为特性嵌套在类定义中。这种区别 有 助 于 避 免 信 息 冗 余, 而 在 3. 2. 5 节 选 择 嵌 入 多 值 联 系 时, 就 像stars出 现 在 表 示Movie 对象的元组中, 结果带来了冗余。2.ODL中, 属性可能是任意的聚集类型, 比如集合。而E/R模型虽然并没有严格规定允许使用的 数据类型, 但通常都认为允 许使用结 构化数据 而不允 许使用集 合或其他 聚集类型构造符。①因而, 像例 3. 4 中的影星地址集之类的值为集合的属性, 在E/R模型中必须将地址集作为一个实体集来处理, 同时定义联系 Lives-at 来连接影星及其地址。3.E/R模型中, 联系可以具有属性, 而ODL中没有相应的概念。3. 3. 1 实体集 到关系的 转换我们首先来考虑不是弱实体集 的情形。3. 3. 3 节将介绍为适应 弱实体集需要做 的修改。对于每个非弱实体集, 我们将建立一个与之同名而且具有相同属性集的关系。该关系·27·①但 有些 E/ R 模型 对属性 类型 的限制 与 ODL 一 样, 允许 使用结 构或 任何简 单类型 的聚 集。
并 不 包 含 与 实 体 集 有 关 的 任 何 联 系 的 信 息; 联 系 用 独 立 的 关 系 模 式 来 处 理, 详 见3. 3. 2 节。例 3. 10 考虑 图 3. 15( 同 图 2. 8) 中 的 三 个 实体 集: Movies( 电 影 ) 、St ars( 影 星) 和Studios( 制片公司) 。实体集Movies的属性是tit le、year、length和filmT ype。因此, 关系Movies 看起来就和 3. 1 节介绍的图 3. 1 中的关系 Movie 一样。 □图 3. 15 电影数据库的E/R图例 3. 11 现 在, 考 虑 图 3. 15 中 的 实 体 集Stars, 它 有 两 个 属 性:name( 姓 名 ) 和address( 地址) 。所以, 我们预料对应的关系St ars应该是:name addressCarrie Fisher123 FMaple St. ,HollywoodMar k Hamill 456 FOak Rd. , BrentwoodHarrison Ford789 FPalm Dr. ,Beverly Hills 该关系与例 3. 3 中建立的关系 Star ( 见图 3. 6) 相似, 而后者有三个属 性, 其中的 两个—— street 和 city—— 构成一个结构化的地址。二者之间的区别是次要的。我们也可以把这里的关系 Stars 设计得和图 3. 6 中的关系 Star 完全相同, 只要修改图 2. 8 中的 E/ R 模型, 使实体集Stars的属性address分成两个属性:street和city即可。 □3. 3. 2 E/R联系到关系 的转换E / R 模型中的联 系也可用关系表示。对 于给定的联 系 R, 它所 对应的关 系具有以 下属性:1. 联系R涉及到的每个实体 集的键码属性或 属性集应该是R对应的关系模式 的一部分;2. 如果R有属性, 这些属性也应该是R对应的关系的属性;如果一 个实体集 在联系R中出现 多次, 必 须为每 次出 现的属 性改 名, 以免出 现重 名属性。同样, 如果同名属性在R本身或R涉及的实体集的属性中出现两次或两次以上, 也必须改名, 以免重名。·37·
例 3. 12 考虑图 3. 15 中的联系 Owns。它把实体集 Movies 和 St udios 联系起来。因此, 我们使 用Movies的 键码, 即title和year, 和Studios的 键码, 即name, 来表 示Owns的关系模式。该关系的一个例子是:title year studioNameStar Wars 1977 FoxMighty Ducks1991 DisneyWayne' s World1992 Paramount 为了清楚起见, 我们采用属性st udioName, 它对应于Studios中的name属性。请 注 意: 上面 的关 系 加上 例 3. 10 中为 实体 集 Movies 构造 的关 系( 见 图 3. 1) , 完 全包含了为例 3. 6 的类Movie构造的 关系( 见图 3. 12) 中 的信息, 只是把 其stars特性排 除在外。 □例 3. 13 同 样, 图 3. 15 中 的 联 系Stars-In也 可 以 转 换 成 具 有 属 性tit le和year( Movie 的键码) 以及属性 starName( 实体集 Stars 的键码) 的关系。图 3. 16 是关系 Stars-In的一 个例子。注意, 该关系加上图 3. 1 包含了图 3. 13 中的信息, 但它们却 没有重复 的Movie( 译注: 原文误为 Star) 类的非键码属 性( length 和 filmT ype) , 而没有非键码属 性将有损图 3. 13 中的关系模式。似乎年份 year 在图 3. 16 中是冗余的。不 过, 这只是 由于这些电影都 不重名造成的。如果有几部电影重名, 比如都叫“King Kong”, 我们将会看到year对找出哪些影星出演某部电影的哪个版本有决定性的作用。 □title year starNameStar Wars 1977 MCarr ie FisherStar Wars1977 MMark HamillStar Wars1977 MHarrison FordMighty Ducks 1991 MEmilio EstevezWayne' s World1992 MDana CarveyWayne' s World1992 MMike Meyers图 3. 16 联系Stars-In对应的关系与从 ODL 设计出发相比, 从 E/ R 图出发转换到关系模式有以下优点:图 3. 17 联系Contr acts( 签约)· 关系通 常都是“规范化”的, 这意 味着将避免 某些从 ODL 描述直接 进行设计时出现的冗余。· 双向的 ODL 联系 将用表示双向 联系的单个关系来代替。例 3. 14 多向 的 联系 也 容 易转 换 为 关系。让我们来考虑图 2. 12( 即这里的图 3. 17)中 的 四 向 联 系 Contract s ( 签约 ) , 它包 括影·47·
5. 1. 6 本节练习 179……………………………………………………………………………5. 2 涉及多个关系的查询 180…………………………………………………………………………5. 2. 1 SQL中的乘积和连接 180………………………………………………………………5. 2. 2 消除属性的二义性 181…………………………………………………………………5. 2. 3 元组变量 182……………………………………………………………………………5. 2. 4 多关系查询的解释 183…………………………………………………………………5. 2. 5 查询的并、交、差 185……………………………………………………………………5. 2. 6 本节练习 186……………………………………………………………………………5. 3 子查询 188…………………………………………………………………………………………5. 3. 1 产生标量值的子查询 188………………………………………………………………5. 3. 2 涉及到关系的条件 189…………………………………………………………………5. 3. 3 涉及到元组的条件 190…………………………………………………………………5. 3. 4 相关子查询 191…………………………………………………………………………5. 3. 5 本节练习 192……………………………………………………………………………5. 4 副本 193……………………………………………………………………………………………5. 4. 1 副本的删除 193…………………………………………………………………………5. 4. 2 并、交、差中的副本 194…………………………………………………………………5. 4. 3 本节练习 195……………………………………………………………………………5. 5 聚合 195……………………………………………………………………………………………5. 5. 1 聚合运算符 195…………………………………………………………………………5. 5. 2 分组 196…………………………………………………………………………………5. 5. 3 HAVING子句 198………………………………………………………………………5. 5. 4 本节练习 199……………………………………………………………………………5. 6 数据库更新 200……………………………………………………………………………………5. 6. 1 插入 200…………………………………………………………………………………5. 6. 2 删除 202…………………………………………………………………………………5. 6. 3 修改 203…………………………………………………………………………………5. 6. 4 本节练习 203……………………………………………………………………………5. 7 用 SQL 定义关系模式 204………………………………………………………………………5. 7. 1 数据类型 205……………………………………………………………………………5. 7. 2 表的简单说明 205………………………………………………………………………5. 7. 3 删除表 206………………………………………………………………………………5. 7. 4 更改关系模式 206………………………………………………………………………5. 7. 5 默认值 207………………………………………………………………………………5. 7. 6 域 207……………………………………………………………………………………5. 7. 7 索引 208…………………………………………………………………………………5. 7. 8 本节练习 209……………………………………………………………………………5. 8 视图的定义 211……………………………………………………………………………………5. 8. 1 视图的说明 211…………………………………………………………………………5. 8. 2 视图的查询 212…………………………………………………………………………5. 8. 3 属性改名 213……………………………………………………………………………5. 8. 4 视图的更新 213…………………………………………………………………………·Ⅷ·
再谈ODL到关系的转换正如 我们 所看 到 的,E/R模 型 中 的联 系 转换 成 关系 和ODL联 系 转换 成 关 系相比, 有时前者能给出更合适的关系数据库模式。我们可 随意地借助于 E/ R 技术把“多对一”和“多对多”的联系分解成单独的关系。这样做有助于消除冗余和元组数目激增—— 当我们企图实现和类一起定义的多值 ODL 联系时, 有时会遇到这种情况。但是,我们再 次提醒读 者, 3. 7 节 将给出一 种把从ODL描述直接 得到的 关系模 式加以 调整的机械方法。星、电 影和两个制片公司 —— 第一个拥有 影星, 第二个 制作电影, 并 约定了影 星在电影 中演什么角色。我们用关系Contracts来表示这个多向联系, 其关系模式的属性由来自下列四个实体集的键码组成:1. 键码 starName( 影星名) 代表影星。2. 由属性title( 名称) 和year( 年份) 组成的键码代表一部电影。3. 键码studioOfStar( 影星所在的制片公司) 表示第一个制片公司的名字; 请记住, 我们假定制片公司名是实体集 Studio 的键码。4. 键码producingStudio( 制 作电影的制片 公司) 表示制 作该影 星所主演 的电影的 制片公司名。注意: 我们为关系 模式中的属性取名 时比较注意, 没有一个属 性叫“name”, 因为 它会引起歧义, 我们不知道它 是指影星的 名字还 是指制片 公司的 名字, 或者, 对于后者 —— 制片公司, 究竟是指哪个制片公司。 □3. 3. 3 处理弱 实体集如果E/R图中出现了弱实体集, 我们需要做各不相同的三件事。1. 弱实体集W本身所对应的关系模式必须既包含W的属性, 也包含有助于构 成W的键码的其他实体集的键码属性。这些辅助实体集是很容易识别的, 因为它们通过双菱形表示的“多对一”的联系与W相联。2. 有弱实体集W出现的任何联系都必须把W的所有键码属性( 包括为W提供键码的其他实体集的键码属性) 作为键码。3. 然而, 正如 我们将要 看到的, 从弱 实体集W到 有助于 为W提 供键 码的其 他实 体集的双菱形联系, 其实并不需要 转换成关系。理由 是这种联系的属 性必然是弱实体 集 W自身属性集的子集, 这样的话, 这些联系除了帮助W找到它的键码之外, 并没有提供 附加信息。当然, 在引入附加的属性来构成弱实体集的键码时, 我们必须注意属性不能重名。必要时, 我们应为其中的一些属性改名。例 3. 15 考虑图 2. 27 中的弱实体集Crews, 我们将其复制为图 3. 18。从该图我们可以得到三个关系, 关系模式分别为:Studios(name,addr)·57·
Crews ( number, studioName )U nit-of(number,studioName,name)第一个关系 Studios 是从同名的实体集直接构 造的。第二个关系 Crews 来自弱 实体集Crews。Crews的键码属性应是该关系的属性。如果Crews还有非键码属性, 它们也应该 包 含 在 关系 Crews 的属 性 集 中。我 们 选 择 与实 体 集 Stutios 的 属 性 name 相 对 应 的studioName作为关系Crews的属性。图 3. 18 弱实体集Crews第三个关系 Unit-of, 来自与它同名的联系。和通常的做法一样, 在关系模型中我们用一个 关系表示 一个E/R联系, 该关 系的关 系模式包 含相关实 体集的 键码属性。在 这里,U nit-of的属性有弱实体集Crews的键码属性number和studioName, 还有实体集Stutios的键码属性 name。但要注意: 由 于 Unit-of 是“多对 一”的联 系, 制片公 司的 studioName自然与制片公司的name是一码事。例如, 假设 Disney crew # 3 是 Disney 制片公司 的一个 电影组, 则 Unit-of 的联系 集包含下述组合:(Disney crew# 3,Disney)它所对应的Unit-of元组为:( 3, Disney, Disney)所以, 我们可 以把Unit-of的属性studioName和name进 行合并, 从而, 给出 一个 更简单的关系模式:Unit-of ( number, name )但是, 现在我们完全可以省略Unit-of关系了, 因 为它的属性是关 系Crews的属 性的一个子集。 □例 3. 16 现在考虑 2. 6. 1 节例 2. 31 和图 2. 28 中的弱实体集Contracts( 签约) 。我们将其复制在图 3. 19 中。关系Contracts的关系模式为:Contracts ( starName, studioName, title, year, salary )这 些 属 性 是 经 过 适 当 的 改 名 处 理 的 St ars 的 键 码 和 Sturdios 的 键 码, 以 及 构 成Movies的键码的两个属性, 还有实体集Contracts自己的单个属性salary( 酬金) 。没有任何关系对应于联系 Star-of, Studio-of 或者 Movie-of。它们中间任何一个的关系模式都将是上面的Contract s的真子集。顺便提 一下, 请注意: 假如 我们从图 2. 13 中的E/R图出 发, 将 得到与 这里完 全一 样的关系。回忆一下, 那幅图中将签约 contracts 视为 stars( 影星) 、movies( 电 影) 和 st udios( 制片公司) 之间的三向联系, 而Contracts还有一个附加的属性:salary( 酬金) 。在例 3. 15 和例 3. 16 看到的现象—— 双菱形联系不需要关系—— 是弱实体集的一种·67·
图 3. 19 弱实体集Contrancts普遍现象。弱实体集 E 对应的关系模式包含了由任何双菱形联系 R 构造的关系模式——其中R是从E到某个有助于形成E的键码的其他 实体集的“多对一”联系。原因是E对应的关系中既 包含了 E 的键码 属性, 也包含了 由 R 连 接的两个 实体集 的所有键 码属性。因此, 我们可以得出下面两条对弱实体集的修正规则。· 如果 E 是 一个弱实 体集, 为 E 构 造一个 关系, 其关系模 式中包 括 E 的键 码属性,还包括通过“多对一”的联系与E相关的“辅助”实体集的的键码属性。· 不要为任何从弱实体集到其他实体集的“多对一”联系构造关系, 因为该联系 是有助于为弱实体集提供键码的双菱形联系。3. 3. 4 本节练 习练习 3. 3. 1: 将图 3. 20 中的 E/ R 模型转换为关系数据库模式。图 3. 20 航空公司的 E/ R 图 图 3. 21 “姊妹”舰的 E/ R 图练习 3. 3. 2: 图 3. 21 中 的E/R图 表示舰艇。如果 两艘舰艇 是根据 同一个方 案设计制 造的, 就称它们为“姊妹”舰。把这个 E/ R 图转换为关系数据库模式。练习 3. 3. 3: 把下面的E/R图转换为关系数据库模式。( a) 图 2. 28。·77·
( b) 练习 2. 6. 1 的答案。(c) 练习 2. 6. 4(a) 的答案。( d) 练习 2. 6. 4( b) 的答案。3. 4 子类结构到关系的转换面向对象的模型和 E/ R 模型在处理子类概念方面有些许不同。这种不同导致了以两种不同的方式构成与类的层次相应的关系。我们先来回忆一下二者的区别:· 在 ODL 中, 一个对象完全属于一个类。对象继承了它的所有超类 的特性, 但 是在技术上, 它并不是其超类的成员。· 在 E/ R 模型中, 一个“对象”可以由属于多个实 体集的实 体来表 示, 其 中这些实 体集通过“属于”联系连接在一起。因此, 连接在一起的实体共同表示这个对象, 并把其特性—— 属性和联系给予该对象。下面我们就来看看这两点如何影响不同的关系数据库模式设计策略。我们应该记住,ODL 模型或 E/ R 模型并不必 然导致这种或那 种方法; 如 果愿意, 我 们可以选择适合 另一种模型的方法。3. 4. 1 用关系 表示 ODL 子类首先, 让我们来看看将 ODL 子类的分层结构转换成关系模式的技术。应该遵循下面的原则:· 每个子类都有自己的关系。· 这个关系用该子类的所有特性( 包括它继承的全部特性) 来表示。例 3. 17 考虑图 2. 22 中的四个类的分层结构。回忆一下这些类的含义:1.Movie( 电影) , 最主要的类。它是本章众多例子中所讨论的类。2. Cartoon( 卡通片) , Movie 的一个子类, 附加了一 个特性: 是和影 星集之间 的联系,称为voice( 配音) 。3. MurderMystery( 谋杀片) , 是 Movie 的另一个子类, 附加了一个属性: weapon( 武器)。4.Cart oon-MurderMystery( 卡 通-谋杀 片) , 是Cartoon和MurderMyst ery的 子类,除了( 天生的) 三个超类的所有特性以外, 它没有附加的子类。Movie的模式和以前一样:Movie( title, year, length, filmT ype, studioName, starName)一些典型的元组见图 3. 13。对于类Cart oon, 我们在六个属性的Movie模式中增加了voice 属性, 给出了七个属性的模式: Cart oon(title,year,lengt h,filmT ype,studioName,starName,voice)对于 类 MurderMystery, 我们构 造了 另一 个关系, 它的 关系 模式 除了 Movie 的六 个属性外, 还包括weapon。也就是说,MurderMystery关系的模式为:MurderMystery( title, year, length, filmT ype, studioName, starName, weapon)最后, 关 系Cartoon-MurderMystery的 模式 不仅 包 括Movie的六 个属 性, 还 包括 另·87·
两个超类的属性 voice 和 weapon。因而, 该关系的模式有八个属性:Cart oon-MurderMystery(title,year,length,filmType,st udioName,starName,voice,weapon) □3. 4. 2 在关系 模型中表 示“属 于”联系透过E/R模型中的isa( 属于) 分层结构, 我们看到这 样一个特点: 分层结构通过 与属于联系有关的实体集进行扩展。因此, 很自然的做法就是为每个实体集建立一个关系, 并且只将该实体集的属性赋给这个关系。然而, 为了识别与每个元组有关的实体集, 就需要包括每个实体集的键码属 性。因为从 E/ R 模型到关系 模型的转换方法把 有关 E / R 属性和联系的信息 分成几个单独的 关系, 结果, 某 个子类的 成员信息 将分散 到几个关 系里, 而这种情形总有可能发生。我们没有为属于联系建立相应的关系, 而把属于联系隐含在这样的事实里, 即与属于联系有关的实体集拥有相同的键码值。图 3. 22 电影E/R图的分层结构例 3. 18 考虑图 2. 22 中的 E/ R 模型的分层 结构。回忆一下, E/ R 图的相关部 分曾见于图 2. 23, 我们将其复制成图 3. 22。表示这部分图所需的关系模式为:1. 关系Movies(tit le,year,length,filmT ype) , 即例 3. 10 中讨论的关系。2. 关系 MurderMysteries( title, year, weapon) 。前两个属性是所有电影的键码, 而最后一个属性是所对应的实体集的唯一属性。3. 关系 Cartoons( title, year ) 。该关系是卡通片的集合。它除了电影的键码之外, 没有其他的属性, 因为卡通片的附加属性都包括在联系 Voices 之中。4. 关系Voices(tit le,year,name) , 对应于影星St ars与Cartoons之间的联系Voices。最后一个属性是 Stars 的键码, 而前两个属性构成了 Cartoons 的键码。注 意, 图 3. 22 中没 有 任何 实 体集 对 应 于 类Cartoon-MurderMysteries。 因 此, 与 例3. 17中的关系设计不同, 没有专门为既是卡通片又是谋杀片的电影设计的关系。对于一部集二 者于一 身的 电影, 我们 从 Voices 关系中 获得 其配 音的信 息, 从 MurderMysteries 关系 中 获 得 武 器 的 信 息, 所 有 其 他 信 息 来 自 电 影Movies关 系 或 者 来 自 涉 及 到 实 体 集Movies, Cartoons 和 MurderMysteries 的任一联系所对应的关系。还要注意, 关系Cartoons的模 式是Voices的关系模 式的子集。在 许多情形 下, 我 们·97·
可能倾向于 删除 Cartoons 关系, 因为看起来它 没有提供 Voices 之外 的任何 信息。但是,在我们的数据库中可能有一些无声卡通片。这些卡通片没有配音, 这样我们就有可能忽略它 们 是 卡 通 片 的 事 实。实 际 上, 同 样 的 问 题 也 以 不 同 的 形 式 出 现 在 例 3. 17 的 关 系Cart oons 中, 在那里, 没 有配 音的卡 通片 是不 会提及 的。这个 问题 可以通 过 规范 化来 解决, 我们将在 3. 7 节详细讨论。 □3. 4. 3 方法的 比较3. 4. 1 节和 3. 4. 2 节的两种方法都有各自的问题。ODL转换把一个对象的所有特性都保存在一个关系中。但我们可能不得不搜索多个关系才能找到一个对象。例如, 使用例3. 17 的数据库模式, 要找到一 部电影的长度, 我们必须搜 索四个不同的关 系, 直到找 到该电影所在的类对应的关系为止。另一方面,E/R转换对于 一个对象( 实体) 所 属的每个实体集 或联系都把 该对象的 键码复制一次。这种重复浪费空间。而且, 我们也许不得不在几个关系中搜寻, 才能找到一个对象的信息。例如, 如果我们想在例 3. 18 的数据库模式中查找某个谋杀片的长度以及所用的武器, 就可能是这种情况。3. 4. 4 使用 NULL 值 合并关系可有多种方法表示类的分层结构。可以使用一个特殊的“空值”, 用 NULL 表 示。当NULL 作为元组的某 个属性的分量出现 时, 这就 非正式地表明该 元组在该属 性上没有 合适的值。空值虽然不是传统关系模型的组成部分, 但实际上它非常有用, 而且在SQL查询语言中扮演着重要的角色, 详见 5. 9 节。如果在元组中允许使用 NULL 值, 我们就可以用单个关系来 表示类的分层结 构。该关系拥有的属性对应于分层结构中任何类的对象所拥有的全部特性。因此, 一个对象就可以用一个元组来表示了。如果不是这个对象的类所拥有的特性, 则该元组对应的属性就取值为 NULL。例 3. 19 如果我们将这种方法应用于例 3. 17, 将会建立一个关系, 其模式为:Movie(title,year,length,filmT ype,st udioName,starName,voice,weapon)像《谁陷害 了兔 子罗 杰》(Who Framed Roger Rabbit?) 这 样既 是卡 通片 又是 谋杀 片的电影, 在没有 NULL 值时可能需要几个元组才能表示; 每个“voice”对应一个元组。①另一方面,《美人鱼》(T he Litt le Mermaid) 是一部卡通片, 但不是谋杀片, 它的weapon分量将取值为NULL。《东方快车谋杀案》(Murder on the Orient Express) 在voice属性上将取值为 NULL, 而《乱世佳人》( Gone With t he Wind) 在 voice 和 weapon 属性上将都为 NU LL。注意: 像 3. 4. 2 节中的方法那样, 这种方法允许我们在一个关系中查找来自分层结构中所有类的元组。另一方面, 像 3. 4. 1 节中的方法那样, 它也允许我们在一个关系中查找一个对象的所有信息。·08·①事 实上,《谁陷 害了免 子罗杰 》中既有 影星又 有配 音, 每个 影星-配音(star-voice) 对 将对 应一个 元组 。一个 纯卡通片的 star Na me 属 性将 取值为 N U LL , 结果把 配音 和其他 信息记 录下 来。
3. 4. 5 本节练 习练习 3. 4. 1: 请把图 3. 23 中的 E/ R 图转换成关系数据库模式。图 3. 23 练习 3. 4. 1 的 E/ R 图练习 3. 4. 2: 图 3. 24 给 出了一 个模式 的ODL描 述, 该模 式类似 于练 习 3. 4. 1 中 的E / R 图。 请把 它转 换 为关 系数 据库 模式。 记住, Course 对象 有 一个“对 象 标识”( objectidentit y) , 可以新建一个属性来表示该OID, 比如,CourseID。在本练习中请不要模仿练习3. 4. 1 中所用的转换弱实体集的策略( 尽管在原则上, 如果愿意, 也可以这么做) 。练习 3. 4. 3: 请把下列练习中的ODL设计转换成关系数据库模式。* (a) 练习 2. 4. 1。( b) 练习 2. 4. 4。练习 3. 4. 4: 请把下列练习中的E/R设计转换成关系数据库模式。* ( a) 练习 2. 4. 3。(b) 练习 2. 4. 4。! 练习 3. 4. 5: 请把图 3. 25 中的 E/ R 图转换成关系数据库模式。 interUface Course{attribute int number;attribute string room;relationship Dept deptOf inverse Dept: :coursesOf;};interUface LabCourse:Course{attribute int computerAlloc;};interface Dept{unique attribute string name;attribute string chair;relati !onship Set< Course> coursesOfinver se Course: : deptOf;};图 3. 24 课程和实验课程的 ODL 描述图 3. 25 练习 3. 4. 5 的 E/ R 图·18·
! 练习 3. 4. 6: 从相 应的ODL定 义出发 得到的关 系数据库 模式, 与练习 3. 4. 5 得 到的会有什么不同?3. 5 函 数 依 赖关系模型中要处理的最重要的一类约束是称为“函数依赖”的单值约束。在关系数据库的设计过程 中, 为了减 少冗余, 需要对数据 库模式进 行重新设 计, 在 3. 7 节 读者将会 看到, 了解这类约束对此是至关重要的。还有一些其他约束, 有助于我们设计出优秀的数据库模式, 比 如 覆 盖 了 3. 8 节 的 多 值 依 赖 以 及 将 在 4. 5 节 提 到 的 存 在 约 束 ( exist enceconstrain) 和独立性约束( independence const rain) 。3. 5. 1 函数依 赖的定义关系 R 上的“函 数依赖”是这 种形式的 陈述 ——“如果 R 的 两个 元组在 属性 A1 , A2 ,…An 上 一致( 也就是, 两个 元组在这 些属性 相对应的 各个分 量具有相 同的值) , 则 它们 在另一个属性B上也应该一致”。这种依赖正式记作A1A2 …An →B, 也可以说“A1 ,A2 , …,An函数决定B”。如果一组属性A1 ,A2 , …,An 函数决定多个属性, 比方说,A1A2 …An →B1A1A2 …An → B2…A1A2 …An →Bm则可以把这一组依赖关系简记为:A1A2 …An → B1 B2 …Bm图 3. 26 提示我们在关系R中, 对于任意两个元组t和u, 这种函数依赖的含义。图 3. 26 函数依赖对两个元组的影响例 3. 20 让 我 们 考 虑 图 3. 13 中 的 关 系Movie, 图 3. 27 是它的一个实例。我们可以合理地断言关于 Movie 关系的几个函数依赖。例如,我们可以断言如下三个函数依赖:tit le year → lengthtit le year→filmT ypetit le year → studioName由于 三 个依 赖 的左 边 完全 相 同, 都 是tit le和 year, 我们 可以 用 简写 的形 式把 它们 汇 总在一行中:title year → length filmT ype studioName这组函数依赖具体地说明了如果两个元组在tit le分量和year分量都有相同的值, 则·28·
函数依赖蕴涵着模式信息记住, 函数 依赖 和任 何约束 一样, 都 是针对 关系 模 式、而不 是针 对特 定 实例 的断言。只看一个实例, 并不能确切地断定某个函数依赖成立。例如, 看了图 3. 27, 我们可能会 假设 依赖title→filmT ype成立, 因 为 对Movie关系 这个 特例 中 的每 个 元 组而言, 正好出现这种情况: 任意两个在 title 上一致的元组在 filmT ype 上也一致。然而, 我 们不 能为关 系Movie断言 这个函 数依 赖。如果 我们的 实例 中 包含 诸如King Kong 的两 个版本 —— 彩 色版本和 黑白版 本—— 的元组, 那么, 上 面的函数 依赖就不成立了。它们在length,filmT ype和st udioName分量上必然也都有相同的值。如果我们记住了导致建立 Movie 关系模式的原始设计, 那么这种断言就是有意义的。属性 title 和 year 构成了电影对象的键码。因此, 我们认为给定了名称和年份, 对应的电影就只有唯一的电影长度、唯一的电影类型和唯一的所属制片公司。title year length filmType studioName StarNameStar Wars 1977 124 color Fox Carr ie FisherStar Wars1977 124 color Fox Mark HamillStar Wars1977 124 color Fox Harrison FordMighty Ducks 1991 104 color Disney Emilio EstevezWayne' s World 1992 95 color Paramount Dana CarveyWayne' s World1992 95 color Paramount Mike Meyers图 3. 27 关系Movie另一方面, 我们观察到, 如下表述:tit le year → starName就是错误的; 它不是函数依赖。已知title和year构成了电影的键码, 我们可能认为上面的依赖也成立。但是, 从Movie类定义的内容来看, 实际情况是每部电影都存在唯一确定的影星集。当我们 从ODL设计转换到关 系模型时, 必须为每部电 影建立 多个元组, 而每 个元组都有不同的影星。所以, 即使所有的元组在Movie类的其他特性上都相同, 它们在影星姓名上也绝不相同。 □3. 5. 2 关系的 键码如果 一个或多 个属性 的集合{A1 ,A2 , …,An }满足 如下 条件, 我们 就称 该集合 为关 系R 的键码:1. 这些属性函数决定该关系的所有其 他属性。也就是说,R中 不可能有两个不 同的元组, 它们在 A1, A2, …, An上的取值完全相同。2. {A1 , A2 , …, An }的任 何真子集 都不 能函数 决定 R 的 所有其 他属 性, 也就是 说, 键码必须是最小的。·38·
函数依赖中的“函数”是什么意思?A1A2 …An →B之 所以 称为“函 数”依赖, 是 因为 原 则上 有这 样一 个函 数: 取 一组值, 分别对应于属性 A1 , A2 , …, An , 结果对应于 B 产生唯一值( 或者是空值) 。例如, 在Movie 关系中, 我们假设一个函数, 取一个字符串, 比如″Star Wars″, 再取一个整 数, 比如“1977”, 结 果 产 生 一 个 唯 一 的 长 度length值, 比 如 124, 这 个 函 数 就 出 现 在 关 系Movie 中。但是, 该函数不是我们在数学中遇到的普通意义上的函数, 因为无法从自变量来计算因 变量。也就是说, 我们不能 对″St ar Wars″这样 的字符串 和″1977″这 样的整数做某些运算而得到正确的电影长度。而只能通过在关系中查找来“计算”该函数。我们查找具有给定的title和year值的元组, 而后看该元组的length值是什么。如果键码只包含一个属性 A, 我们通常就称 A( 而不是{A}) 为键码。例 3. 21 属性组{title,year,st arName}构成了图 3. 27 中的Movie关 系的键 码。首先, 我们必须证明它们函数决定所有其他的属性。也就是说, 假设两个元组在这三个属性——title,year和starName上取值一致。由于它们在tit le和year上取值一致, 正如 我们在例 3. 20 中所 讨论 的, 它 们必 然在 另外 几个 属性lengt h,filmT ype和studioName上 取值一致。因此, 两个不同的 元组不可能在 title, year 和 starName 三个属性上 全都取值 一致; 它们实际上就是同一个元组。现 在, 我们必 须证 明{title, year, starName}的任何 真子 集都 不能函 数决 定所 有其 他的属性。为什么呢? 首先我们观察到 title 和 year 不能决定 starName, 因为许多电影有多个影星。因此, {title,year}不是键码。{ year, starName}也不是键码, 因为一个影星在同一年中可能出演两部电影; 因此,year starName→tit le不是函 数依赖。同样, 我们断定 {title,st arName}也不是键码, 因为不同年 份可能会制 作两部同名电影。而且这两部电影可能有一个共同的影星, 虽然坦白地讲, 我们还没有想到一个例子。①□有时一个关系有多个键码。这样的话, 通常要指定其中的一个键码为主键码( primarykey) 。在商业数据库系统中, 主键码的选择会影响到某些实现细节, 比如关系在磁盘上如何存储。3. 5. 3 超键码包 含键码的 属性集称为“超键码”( superkey) , 是“键 码的超集”( superset of a key) 的缩写。因此, 每个键码都是超键码。但是, 某些超键码不是( 最小的) 键码。注意, 每个超键码都满足键码的第一个条件: 函数决定它所在的关系的所有其他属性。但是, 超键码不必满足键码的第二个条件: 最小化条件。·48·①记 住, 函数依 赖是 我们对 数据 所做的 假设或 断言 。没有 任何 外面的 权威 可以给 我们 一个 绝对 的结 论, 判 断一个函数 依赖 是否成 立。所 以, 关于 依赖 的成立 性, 我们 可以做 出任何 似乎 合理的 假设 。