视图更新与关系数据库理论

978-7-115-43546-0
作者: 【美】C.J. Date(达特)
译者: 田远帆
编辑: 胡俊英陈冀康

图书目录:

详情

本书侧重介绍数据库架构的设计和实现。本书讨论了如何实现视图的更新,并且让这些更新不会违反关系数据库的完整性约束。本书介绍了如何让更新能够工作,而不管其目标是基础表还是视图。通过阅读本书,读者能够改进未来的数据库产品设计、更好地实现数据库的设计,确保数据库设计的关键任务圆满实现,让关系数据库设计表现出用户真正期待的性能。

图书摘要

版权信息

书名:视图更新与关系数据库理论

ISBN:978-7-115-43546-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著     [美] C.J. Date

  译     田远帆

  责任编辑 陈冀康

  执行编辑 胡俊英

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright© 2013 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2016. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书是数据库专家C.J. Date的又一部力作,详细介绍了数据库架构的设计和实现,同时讨论了视图更新及关系数据库理论。

全书内容分为15章,用示例和理论相结合的方式,介绍了关系数据库的相关理论,还对视图作了深入探究。书中不仅介绍了一些基础的数据库操作,同时重点结合示例给出了视图相关的操作和讲解。通过阅读本书,读者能够在实际工作和学习中,改进自己的数据库设计,更好地实现数据库相关任务。

本书是经典的数据库参考指南,非常适合数据库架构师、数据库设计人员以及相关领域的研究人员参考阅读。


内涵的外延

Edgar F. Codd发明了一种概念

就是我们知道的视图

现在的视图和基础关系变量

可相互转换

让我们高唱一曲

关于视图的布鲁斯

——Anon.:《Where Bugs Go》

 

“奥蒙德的公爵昨天审视了他的部队的全景,命令把所有的枣红色马和灰色马都换成黑马。”

——目前所知的最早关于视图更新的案例(1693),引自《Oxford English Dictionary》中的《A Brief Historical Relation of State Affairs 1678–1714》,作者Narcissus Luttrell (1857)

 

浅尝辄止是如此危险;

诗泉之水若不深饮,弗如不尝:

浅浅一酌让人头脑发热,

尽兴狂饮却让人重新清醒。

——Alexander Pope《An Essay on Criticism》(1711)

 

— ••••• —

 

献给我最爱的妻子Lindy、女儿Sarah和Jennie


C.J.Date是一位专攻关系型数据库技术的独立作家、演说家、研究员和顾问。他最著名的著作《数据库系统导论》(第8版,Addison-Wesley,2004)销量已经突破850000册,并被全球数百所高校作为教材使用。他同时还撰写过许多其他数据库管理相关书籍,包括:

Date先生于2004年正式进入计算机行业名人堂。他以能够用简明易懂的方式解释复杂的技术问题而著称,造诣之高,无出其右者。


本书是这个系列的第3本书,它的两位“前辈”是:

以上两本书于2012年由O’Reilly出版发行。第1本书的目标读者是所有种类数据库的从业人员,书中解释了关系理论的基本原理,以及以此为基础如何将SQL当作一个关系型数据库使用(在那本书中我使用的一条准则是“关系化地使用SQL”)。第2本书则针对性强一些,它瞄准的读者群是那些对数据库设计感兴趣的行业专家,书中解释了关系型数据库设计的一些理论,并展示了为什么这些理论如此重要。而第3本书,也就是本书,则针对性更强。本书专注于对一个非常关键的问题的研究,而这个问题则涉及关系型数据库应该如何运行(与目前大部分商业SQL数据库系统的运行表现恰恰相反)这一核心内容。这个问题就是“更新理论”。这个理论正如本书的题目显示的那样,适用于视图的更新——不论是在一般情况下,还是特定情况下。同时,本理论也适用于“基础数据”的更新。

注意:尽管我的理论包含上面的最后一句话,但我还是决定在本书的题目中更加强调对视图的更新,因为据我观察,一般数据库从业者相信他们自己能够理解对于基础数据为对象的更新是如何运作的,而一旦对象变成视图,他们的典型反应就是极度怀疑无论使用什么办法,对视图的更新能否真的实现。其实我一直很奇怪,关于视图更新的讨论居然曾经是并且依旧是一个有争议性的话题,当然这也是最初让我决定撰写这本书的一个重要原因。

顺带说一句,关于前两本书,我要对本书中大量引用它们(特别是第1本)而表示歉意。目前,本书中大部分引用的其他出版物都会给出全名,例如下面的例子。

David McGoveran:“Accessing and Updating Views and Relations in a Relational Database”美国,专利号7263512(2007年8月28日)

不过从现在开始,当我提到之前出版的著作时会使用简称,即《SQL and Relational Theory》和《Database Design and Relational Theory》。

作者注:我说过我会用全名引用其他的著作,不过其实也没有多少能引用的。虽然大量关于视图更新的论文、著作或者其他文章在最近30年内层出不穷,但它们中大多数,包括David McGoveran发表的某些例外的特定著作,所主张的论调和本书所体现的论点大相径庭(请参看前言后面部分关于这一点的讨论)。因此大多数时候,我都觉得不适合引用这些著作,除了偶尔因需要提及一下。如果你有兴趣详细地了解一下这些“其他的论调”,那么可以在《An Introduction to Database Systems》(第8版,Addison-Wesley,2004)的第10章中找到一个关于这些书籍名称的简短列表。

在这里我需要强调的是本书假定你们完全了解《SQL and Relational Theory》所讨论的内容。举例来说,我认为你们理应知道什么是关系,什么是属性,什么是数组,所以,我不会因为未对这些基础名词做提前解释而道歉。本书的目标读者是数据库相关专业人士,而专业人士应该对我之前的书所包含的这些基础知识很熟悉才对。为了使本书内容更加独立,我会在第2章“技术背景”中对前面的书的相关部分做一个简短的回顾。同时我也会在第3章“视图概念:近距离观察”中给出一个关于什么是视图以及它们应该如何运作的详细综述。

我的目标读者群是数据库相关专业人士,进一步说也可以是所有对关系模型、关系型技术或者关系型系统感兴趣的人。如前所述,对《SQL and Relational Theory》熟悉的话会对阅读本书有很大帮助,不过我相信本书无论对关系理论整体而言,还是对视图更新具体来讲,都会有新鲜的见解。而且,我认为值得指出的是,我很有可能可以利用这里面的一些想法在没有数据库管理系统(DBMS) [1]支持的情况下来指导一个“自助式”的实施(针对视图更新)。但在这里其实我真心希望数据库管理系统工程师们能够看到这本书,并由此能在他们自己的产品中提供对视图更新的支持。注意:我同时也想提一下,我会举行一场基于书中内容的现场研讨会。想了解更多内容,请访问下面网址:www.justsql.co.uk/chris_date/chris_date.htm

如之前所说,我假设你知道什么是关系、属性、数组。更进一步来讲,我假设你也至少大体知道什么是视图。视图最初被讨论(当时并不用“视图”这个名字)是在Codd的第一篇关于关系模型的论文中:

E.F.Codd: “Derivability, Redundancy, and Consistency of Relations Stored in Large Data Banks,” IBM 研究报告 RJ599(1969年8月19日)

现在,正如Codd自己在这篇论文中所预见的,支持视图最主要的理由,是原则上可以通过它们来达成逻辑数据独立这个重要目标(逻辑数据独立指的是能够改变一个数据库的逻辑,而不需要相应改变用户的使用习惯的能力,由此可以保护针对既有用户培训、既有应用以及其他部分的投入。在第3章我们会有更深入的讨论)。换句话说,视图存在的主要理由,严格来讲就是为了实现逻辑数据独立这个目标。但如果我们想在实际中实现它,而不仅仅是停留在原则上,那么视图必须是可更新的。

所以针对视图的更新是一个很重要的问题。由此重要性导致的结果是,无论是对商业领域,还是研究领域而言,视图已经成为大家关注的焦点有一段时间了(至少有35年左右),而且一些不同的方案已经被提出,有些甚至被实施过。但可惜的是,所有这些方案都没能对视图更新给出一个令人满意的解决办法(这里我要强调,不仅仅是我,其他作者也这么认为)。我们举例来说,在当今主流的SQL产品中,视图更新机制具有以下两个典型的缺陷。

(再次说明,第3章中有关于这些问题的深度讨论。)至于那些研究文献,我觉得它们通常都会忽视一些重要因素,这些因素对于一个系统化的、综合性的以及正确的解决方案至关重要。与前面的例子不同,我相信在本书中将要详细讲解的,恰恰就是一个“系统化的、综合性的以及正确的” 解决方案。同时我也相信(在这里我必须说明,我本身并不是一名工程师),这个解决方案可以被整合进关系型数据库管理系统,并给系统架构带来适度的概念化扩展。

作者注:请注意我很谨慎地提到“关系型数据库管理系统”。在后面你将会看到,我提出的这个解决方案非常依赖对完整性约束的声明(当然,这需要数据库管理系统的强制执行能力来做保障)。从我的角度来说,我认为这些能力是构成一个真正的关系型系统的必要条件。不过相信大家都知道,当今绝大部分的SQL产品在这个环节都有缺陷。现在我正式介绍一下本书的大体结构。

注意:目前给大家描述的大纲其实已经足以说明本书是按照循序渐进的方式来撰写的,我本人也强烈建议大家按照写作顺序来阅读本书。

还有几点我要在这里提前说明。首先请注意,在本书中我按照惯例使用一般术语,用小写字母表示的“更新”是代表包含“INSERT”“DELETE”以及“UPDATE”等所有操作语句的总称(正如我刚刚所提到的“所有重要的关系赋值操作”,详见第2章)。如果想表达“UPDATE”这个操作语句,我会全部使用大写字母表示,至于“INSERT”和“DELETE”操作就不会引起混淆。不过一味地使用大写会显得有些烦人,尤其是当它们作为修饰语使用的时候,例如“INSERT规则”(“插入规则”?)。所以,我决定在本书中两种格式一起使用,我会根据前后语境自我发挥(我也不会装作在这方面前后有多么一致)。

第二点,请注意我使用“SQL”来表示SQL语言的标准版,而不是什么专有的术语(另有明确说明的除外)。特别是按照标准读音“SQL”应该念为“ess cue ell”,而不是“sequel”(因为现在很多地方都按后者来发音),所以我对“一个SQL表”的书写会按照标准读音的写法。注意:SQL标准版随着时间推移出现了数个版本,在本书写作的时候最新版本是SQL:2011。以下是完整的版本信息。

国际标准化组织(ISO):Database Language SQL, Document ISO/IEC 9075:2011 (2011)

第三点也是最后一点,我需要对我所谓的“用户”做一下说明,特别是我需要解释一下经常用到的一些短语,如“用户看到的”或者“用户对数据库的感觉”。总体来说,你可以把“用户”这个词理解为一个交互用户 [2]、一个应用程序开发人员或者两者都是,请根据上下文理解。至于“用户看到的”和类似的短语中的用户,我指的是那些并未与整个数据库交互而更多的是与一些子集交互的用户,根据“子模式”定义。另外,根据视图的机制,子集可以并且经常参与到一些逻辑重建中。实际上,我们可以(至少我会这么做)简单而不失全面地假设,子集是由视图组成的,即使一些由基本数据转换而来的视图实际上跟导出它们的基本数据等价。当然,对于这个子集的用户来说,视图的集合就是数据库本身!换句话说,“数据库”在某种意义上来说是个相对项。所以,至少是为了本书使用,我们可以稍微有点随意但有效地把数据库定义为一个给定的数据,例如给定的基本数据的集合,或者是这个集合下的一些具体的,很可能是经过重构的子集。注意:当我在这里说到“随意”的时候,我考虑到的是这个数据库不仅仅只有数据,如相应的完整性约束也需要被考虑进去,我们在第2章和第3章将会见到。

[1] 数据库管理系统(DBMS)= database management system。很显然,数据库管理系统和数据库是有区别的!然而不幸的是,业界通常都把一些商业产品,如甲骨文,或者安装在特定电脑上的此类产品的特定版本称为“数据库”。在本书中我不会跟随这个潮流。问题在于,如果你把数据库管理系统称作“数据库”的话,那你要怎么称呼真正的数据库呢?

[2] 这些用户依然是那些懂一点数据库问题的人,而不是那些纯的“最终用户”,他们可能对这些问题完全没有概念。


在关系型数据库及其实际应用领域中,一直有两个特别棘手和富有争议的问题,而业界并没有得出一个大家都能满意的解决办法,这两个问题是:信息丢失问题和视图更新问题。对第1个问题,Chris Date在过去30年左右的时间里已经撰写了大量著作,现在他开始着手应对第2个问题。

当然他在之前并非没有涉及视图更新这个问题。在他著名的著作,被作为教科书广泛使用的《An Introduction to Database Systems》中就有对该问题的讨论。在1975年第1版发行的时候,书中就有少量篇幅涉及这个问题;到第8版时(2004年),书中已经有多达16页左右的篇幅来进行讨论。他第一次用整个章节讨论这个问题是在1986年开始撰写系列丛书《Relational Database Writings》的时候。到了1995年,在该系列第4本书中,他和David McGoveran带给了我们两个章节的内容来展示对这个问题思考的变迁,这些内容更多地源自McGoveran的研究。这个思考后来在《Databases, Types, and the Relational Model: The Third Manifesto》(2007)的附录中进化,在《Database Explorations》(2010)的章节中成长,最终到了今天的地位。

最初由E. F. Codd在1969年提出的基础理念,至今一直都没有变化。想象现在给定一个数据库,根据定义是由(a)可变关系或者“关系变量”[1]组成的集合,同时由(b)一套完整性约束管理着这些关系变量的可用值。这些给定的关系变量都是“基础”变量。总体来说,被选出的设计是在许多个设计中可以被选出来代表完全相同信息的那一个。从被选出的设计中我们可以根据基础关系变量的关系表达式,通过定义虚拟关系变量,从而得到一个备选方案。由于各种各样的原因,这样一个备选设计,实际上是一个备选的数据库视图,可能相对基础设计来说更适合某些特定用户。更重要的是,这个备选设计可能实际上并不包含底层的或者“真正的”数据库。有些用户对这些其实也不感兴趣,或者根本没有权限看到。除此之外,如果必须针对基础设计做出一些变化,代表原始设计的虚拟关系变量可以在新设计上被重新定义,这样一来已经存在的用户视图就无需改变,那些可能发生的令人讨厌的突变就可以避免。这就是著名的终极目标“逻辑数据独立”背后的基础理念。

如果用户把虚拟变量视为他们的数据库,当实施与虚拟关系变量冲突的数据库更新时,棘手的问题就出现了。数据库管理系统到底是如何判定那些对真正的数据库实施真正的更新会对虚拟关系变量产生特定的改变的?如果这里有数种方式可以实现需要的效果,那么到底该选用哪种方式呢?举个简单的例子,假设一个用户使用常见的“供应商与零部件”数据库(第1章有详细介绍),他看到一个虚拟关系变量或者视图命名为“PS”,其中只显示位于巴黎的供应商。毋庸置疑,PS的定义表达式是“S WHERE CITY = 'Paris'”。现在,我们假设这个用户让数据库管理系统从视图PS中删除数组S2。那么数据库管理系统是否应当这样理解用户的意图:供应商S2不存在了,需要从基础关系变量S中删除相应的底层数组?还是说应当因为用户表述不清而拒绝客户的删除指令,因为将供应商S2的CITY值换成巴黎以外的地方也会达成相同的效果?或者在另一种情况下,假设用户知道供应商S2已经搬迁到伦敦,因此尝试在视图PS中对S2进行“更新数组”的操作来达到从视图PS中删除数组S2的效果,那么数据库管理系统是否应当接受这个更新操作呢?现在我们进一步假设视图PS没有STATUS(状态)属性。那么数据库管理系统应当如何回应该用户对视图尝试插入数组的操作,而用户要求这些数组必须要删除STATUS值?

针对以上和更多的此类问题,Date都试图在他详细的、全面的、细心的、有条理的分析中为我们做出解答。他计划在最开始的3个章节中对这些问题发起进攻。他清楚地定义了“2个数据库被设计为在表达相同信息的时候是等价的”是什么意思,然后在接下来的10个章节,他将对他所使用的方法进行详细讲述。这个方法需要轮流检验每一个关系代数中的操作。例如,那个“只包含巴黎供应商”的视图PS被他称为约束视图,比如,定义一个虚拟关系变量只使用约束操作。同样地,现在定义一个不包含PS中STATUS属性的视图使用投影。既然这个视图是一个约束的投影,我们可以来推断它的更新效果。首先根据Date的更新规则通过投影来决定对底层约束的效果,然后根据更新规则通过约束来决定对底层基础关系变量S的效果。

对由一些关系操作定义的视图应用这些规则的时候会产生一个非常有趣,甚至可以说是很有争议性的问题,这个问题Date在第14章会做阐述,那就是如果两个表达式在语法上不同,但逻辑上等价。例如,数学表达式x(y+z) 和xy+xz在语法上不同,但逻辑上是等价的。那么定义于它们之上的视图在执行更新操作的时候运行行为(如过程、动作或效果等)是否一定要完全相同?

现在,Date的一些观点在我前面提到的2007年和2010年的出版物上出现的时候,被证明是具有争议性的。例如,当我们向R1和R2的合并视图插入1个数组时,这个数组是否应该同时出现在R1和R2之内?如果我们从R1和R2的交集视图中删除一个数组时,是否会导致这个数组从R1和R2中同时消失呢?作为针对那些具体观点表达不同意见的一员,在这里我要强调,在我和Date长时间的合作过程中,我们之间唯一非常严重的技术分歧已经出现。这些争议点在本书中也有展现,并且Date已经针对这些问题加强了基本原理的解释,不过这可能仍然无法让所有人都信服他的观点。而我发现,在他的最后一章“歧义问题再回顾”中出现了希望之光,就好像是隧道尽头的那一盏明灯。在其中他描述了一个想法的大概轮廓,由David McGoveran提出的一种与以往我们更新关系型数据库所采用的方式完全不同的方式,它可以很有效地替代,或者至少是拓展,我们所熟知的“插入”“删除”和“更新”这些早在关系型时代之前就与我们紧密相连的操作。而这种不同寻常的方式所带来的好处是,我之前提到的会产生争议的问题根本不会出现了。

Date告诉我,他并没有期待,甚至没有想过让本书成为他关于视图更新故事的结尾,但他希望本书能够给现存的争论提供一个坚实的理论基础,以使得整件事情可以向前推进。我想这正是他在本书中所提供的,我在这里也希望本书对你有相同的效果。

Hugh Darwen

于英格兰Shrewley区

2013年

[1] SQL喜欢称这些关系变量为“表”。关于更多对于关系变量的名词解释和相关内容,请参看第2章。


我想再次感谢我的夫人Lindy对我在撰写本书期间以及在撰写本系列前作时的大力支持。我还要感谢我的挚友和同事Hugh Darwen、David Livingstone以及David McGoveran,他们对本书的早期草稿给予了大量细致的、综合的审阅。他们和他们所做的大量工作对本书都起到了重要作用,但我特别要感谢David McGoveran。首先,感谢他最开始为本书的主题所提出的一些建议和想法,视图更新在本书中的描述大都基于此发展而来;其次,感谢他就这个主题在过去20年间跟我所做的沟通和协调;最后,感谢他在这个领域所进行的深远的理论工作。而且David所做的工作还大大超出了审阅本身,他不仅仅对词句给出建议,实际上他还针对本书主题的很多方面汇编并提供了许多短篇的论文或笔记。这些笔记在我重新修改文章的时候给了我非常大的帮助,我相信正是这些笔记让我的文章大有起色。当然,我并没有把他给的所有建议都采纳进来,我相信没有哪个作者完全按照审阅者的建议来行文!但是我已经尝试着尽可能采纳那些对我来说最重要和最真实的建议了。当然不用说,任何遗留的错误都是我自己的责任。

C.J.Date

于加利福尼亚,希尔兹堡

2013年


身教胜于言教。

——Samuel Johnson《Rasselas》(1759)

本书中所涉及的大多数范例都是基于我们耳熟能详的(可别说是老掉牙的)“供应商与零部件”数据库而来的。我为再一次把这个老生常谈的例子拿出来表示歉意,但正如我在其他地方所说,我认为在各种不同的出版物上使用同样的例子对于学习而言是有利无害的。就SQL来说[1],数据库包含3张表,更确切地说,是3张基表,分别称为供应商表S(“suppliers”)、零部件表P(“parts”)和设备供应表SP(“shipments”)。样本值如图1.1所示。

图1.1 供应商与零部件数据库的样本值

这几张表简要的语义如下。

现在让我们把重心放在表S上。在本章的其余部分,我们基本上不再关注表P和表SP,只在个别地方提及。以下是表S的SQL定义。

CREATE TABLE S
   ( SNO VARCHAR(5)    NOT NULL ,
     SNAME VARCHAR(25) NOT NULL ,
     STATUS INTEGER    NOT NULL ,
     CITY VARCHAR(20)  NOT NULL ,
     UNIQUE ( SNO ) ) ;

如前所述,虽然表S是基表,但我们仍然可以定义任意数量的“基于”这个基表的视图。这里有两个例子——伦敦供应商LS(“London suppliers”)和非伦敦供应商NLS(“non London suppliers”)。

CREATE VIEW LS /* London suppliers */ AS
   ( SELECT SNO , SNAME , STATUS , CITY
     FROM S
     WHERE CITY = ‘London’ ) ;

CREATE VIEW NLS /* non London suppliers */ AS
   ( SELECT SNO , SNAME , STATUS , CITY
     FROM S
     WHERE CITY <> ‘London’ ) ;

这两个视图的样本值与图1.1基表的对应关系如图1.2所示。

图1.2 视图LS和NLS的样本值

我将在本章中以视图LS和NLS为基础来阐述整个范例,以求抛砖引玉。实际上,我相信视图都是可更新的,这似乎与本领域内的流行说法和最常见的认知格格不入,但在这里我将向大家展示一些关于这个问题基本的理念,以及为什么我会这样想。(不过请注意:在现阶段我只会对这个吸引眼球的想法做必要的说明,详细的探讨我们放到后面的章节再来进行。)

到目前为止,表S是基表,LS和NLS是视图。现在仔细观察,你会发现其实我们还可以用另一种方式来看它们,那就是把LS和NLS作为基表,而S作为视图,具体如下所示。

CREATE TABLE LS 
   ( SNO    VARCHAR(5)  NOT NULL ,
     SNAME  VARCHAR(25) NOT NULL ,
     STATUS INTEGER NOT NULL ,
     CITY   VARCHAR(20) NOT NULL ,
     UNIQUE ( SNO ) ) ;

CREATE TABLE NLS
   ( SNO VARCHAR(5) NOT NULL ,
     SNAME VARCHAR(25) NOT NULL ,
     STATUS INTEGER NOT NULL ,
     CITY VARCHAR(20) NOT NULL ,
     UNIQUE ( SNO ) ) ;

CREATE VIEW S AS
   ( SELECT SNO , SNAME , STATUS , CITY
     FROM LS
     UNION
     SELECT SNO , SNAME , STATUS , CITY
     FROM NLS ) ;


为了保证现在这个设计与原先的设计完全等价,我应该声明并且让数据库满足特定的完整性约束,当然其中应该包括针对CITY值的一致性约束,以保证LS中每一个CITY值都是London,NLS中CITY值都不是London。但是现在我们暂时不考虑这些细节,稍后我会对这些问题做更详细的解释。

那么现在,做出上面这个变化所要表明的内容可以概括为:对基表和视图的设定是没有限制的,是可以互换的(至少从正式的角度来看是这样)。换句话说,对于这个例子,我们至少可以用两种方式设计数据库,这两种方式逻辑上不同,但包含的信息是等价的。(这里的等价信息,是指两个设计代表了相同的信息,也就是说对于在一种设计执行的查询,一定有在逻辑上和它等价的查询可以在另一种设计上执行。第3章将详细阐述这个概念。)而“可交换性原则”就是对上面内容的总结。

关于这个原则,下面几点值得注意。

另一个由可交换性原则引出的问题是表S、表LS和表NLS的行为不应该由基表和视图的分布来决定。为了解释这个问题,我们先假设它们全都是基表。

CREATE TABLE S     ( ... , UNIQUE ( SNO ) ) ;
CREATE TABLE LS    ( ... , UNIQUE ( SNO ) ) ;
CREATE TABLE NLS   ( ... , UNIQUE ( SNO ) ) ;

这样一来,所有的表就都受到约束的限制了。不过从实际情况来看,想在SQL中构造这些约束条件会非常复杂,因此为了便于表达和理解,我将用普通话来描述一下这些约束条件(大部分是大白话)。

当然,正如前面所说的几点所示,这些约束并不是完全独立毫不相干的,其中一些约束是另一些约束逻辑上导致的结果。

现在,为了确保之前章节提到的约束在后续的某些特定更新升级完成后依然有效,就需要进行相应的补偿性操作。通常情况下,补偿性操作也称为补偿操作,是一种由数据库管理系统自动执行的额外更新(不包括用户自行请求的更新),其目的就是为了避免完整性冲突的发生[5]。级联删除就是一个典型的例子[6]。事实上,有一点我们要明确,对于现在手上这个例子来说,我们很需要在进行DELETE操作时应用级联方式。具体来说,不管是从表LS,还是表NLS中,删除数据都需要级联以使得表S中相同的数据行也能够被删除。于是我们可以设想一些补偿性操作,其实就是级联删除规则,观察以下伪代码。

ON DELETE d FROM LS : DELETE d FROM S ;

ON DELETE d FROM NLS : DELETE d FROM S ;

同样,从表S中删除行也需要级联以使表LS或表NLS中同样的行一并被删除。

ON DELETE d FROM S : DELETE ( d WHERE CITY = ‘London’ ) FROM LS ,
                     DELETE ( d WHERE CITY <> ‘London’ ) FROM NLS ;

这里我要特别提醒的是,任何想对并不存在的行进行删除的尝试都是无效的,因此我们可以将圆括号内的表达式直接替换为d。不过从定义方式的角度来讲,圆括号内的定义表达方式更好,因为它们对变量的定义更加具体明确。

类似地,我们也需要为INSERT操作设定补偿性操作,即级联插入规则。

ON INSERT i INTO LS : INSERT i INTO S ;
ON INSERT i INTO NLS : INSERT i INTO S ;
ON INSERT i INTO S : INSERT ( i WHERE CITY = ‘London’ ) INTO LS ,
                     INSERT ( i WHERE CITY <> ‘London’ ) INTO NLS ;


当然,级联插入的概念并不经常与外键约束联系在一起,但这个概念并没有问题。重要的是我们要认清补偿性操作并不总是以简单级联的形式出现。我们在本章中所讨论的仅仅是个简单的例子,以便大家理解概念,在实际工作中则需要根据具体情况来应用各种更复杂的形式,在后面的章节我们也会有一些案例给大家展示。

在当前的范例中,UPDATE操作可以被看作DELETE和INSERT的结合。因此,简单地说,对本范例的必要的补偿性操作就是对DELETE和INSERT操作规则设定的总和。例如,考虑下面表S上面的UPDATE操作。

UPDATE S
SET    CITY = ‘Oslo’
WHERE  SNO = ‘S1’ ;

产生的结果如下。

1.表S中供应商S1现有的行被删除,CITY值为Oslo的新行被插入到这个表中。

2.表LS中供应商S1现有的行同样被删除,这是因为从表S到表LS的级联删除规则。同时因为CITY值为OSlo,这个供应商的新行被插入到表NLS,这次则是应用了级联插入规则。换句话说,供应商S1的行从表LS迁移到了表NLS(当然,这只是笼统来看)。

现在我们假设原来的UPDATE操作是在表LS中执行,而不是在表S中执行的。

UPDATE LS
SET    CITY = ‘Oslo’
WHERE  SNO = ‘S1’ ;

产生的结果如下。

1.供应商S1现有的行从表LS中删除。

2.尝试向表LS插入供应商为S1的新行,CITY值为Oslo。但是该尝试会失败,因为它违反了表LS中CITY值必须为London的约束。因此这次UPDATE操作整体失败,之前已经完成的步骤(在表LS中删除供应商S1的行)被撤销,最后的结果就是数据库保持不变。

现在我要开始讨论本章的核心概念。如果前面两段中所涉及的表中有一部分或者全部都是视图的话,那么我们讨论的所有结论依旧成立,没有改变。例如,像之前一样,假设S是表,LS和NLS是视图。

CREATE TABLE S      ( .............. , UNIQUE ( SNO ) ) ;
CREATE VIEW  LS  AS ( SELECT ... WHERE CITY = ‘London’ ) ;
CREATE VIEW  NLS AS ( SELECT ... WHERE CITY <> ‘London’ ) ;

现在假设用户只能看到视图LS和NLS,但是希望像基表一样操作它们。在这个用户看来,这些表的语义如下。

LS:供应商SNO是已经签约的,名称为SNAME,有状态值STATUS,位于城市CITY中(London)。

NLS:供应商SNO是已经签约的,名称为SNAME,有状态值STATUS,位于城市CITY中(非London)。

此用户将会了解有下面的约束存在(注意这些约束没有提到表S,因为用户并不知道表S的存在)。

但是,该用户并不会意识到有补偿性操作的存在,因为他并不知道LS和NLS实际上是表S的视图。如前所述,该用户甚至根本不知道表S的存在,因此他也并不知道实现这些操作的约束到底是什么,以及视图LS和NLS合起来就等于表S这个事实。该用户执行的针对视图LS和NLS的更新也都会生效,在他看来就像LS和NLS都是基表一样。同样,这个用户在视图LS和NLS执行的更新在S中也会产生对应的关联效果,只是他看不到而已。

现在我们假设用户只能看到视图LS(而不是视图NLS和基表S)。可以想见这个用户仍然希望可以将视图LS当作基表一样操作。当然,这个用户知道表的语义。

LS:供应商SNO是已经签约的,名称为SNAME,状态值为STATUS且位于城市CITY中(London)。

并且了解如下约束。

很明显,这个用户无法向这个表插入新行,也不能更新表中的供应商编码,因为这样的操作很可能违反约束规则,而用户并不知道这些约束(必须不知道)[7]。但是如果LS是一个基表,那么用户完全可以插入新行。否则,这张表将永远是空的!那么,前面所说的事情不就违背“可交换性原则”了吗?

事实并非如此。尽管这个特定的用户不被允许向这个表插入新行,但并不是所有用户都不可以。这个用户不能插入新行到LS的基本原因是他只是管中窥豹,无法看到全局。对比来看,我们在上一部分所讲的用户可以同时看到LS和NLS,它们合起来等价于表S,因此该用户相当于能够看到全局,也由此他就可以向LS(和/或是NLS)插入新行。但是只能看到LS的用户等于只能看到一部分信息,所以一些特定操作是不允许他执行的。

最后,值得指出的是即便我们遇到的数据格式都是基表,用户也会遇到类似的情况。也就是说,即使涉及的表都是基表,有时某些用户也会被禁止在某些特定的表上执行某些特定的更新。举个例子,让我们想想只能看到基表SP,但是看不到基表S的用户。他会和只能看到表LS的用户一样,无法执行插入操作,因为这样的操作同样可能违反用户并不知道的(必须不知道)约束规则,这个约束就是从SP到表S和P的外键约束。

抛砖引玉到此就要告一段落了。我们所用到的这个例子非常简单,从中得出的结论也是显而易见的。而其实我是想通过这个例子引出下面的理念,就是根据实际的定义把视图看作与特定的表相附生的基表,这对于整体考虑如何解决视图更新的问题是很有建设性的。我认为它不仅具有建设性,而且是一种“逻辑上正确”的方法。[8]总体思路如下。

1.视图定义表达式包含了某些特定的约束。例如,视图LS(“London suppliers”)的视图定义表达式表明了LS等同于表S中CITY值为London的约束。

2.这种约束反过来表明了特定的补偿性操作(如那些为了避免违反完整性约束,而需要在用户自行请求的更新之外进行的操作)。举例来说,正如前面讲到的,表S、LS和NLS的约束就代表相应的级联删除和级联插入。

这里我要强调第2点——这点很重要,就是在某些状况下,补偿性操作可以由相关视图的定义表达式决定。换句话说,这样的操作不需要显式指定,也不需要给已经超负荷的DBA[9]增加工作负担。对于这个问题和其他在本章中引出的问题,我都会在后面的章节进行更详细的探讨。

最后,如果你像大多数人一样跳过了前言直接开始阅读第1章,那么现在是时候翻回去阅读前言了,这对你继续阅读下面的章节会很有帮助。前言包含了本书的整体结构,同时还阐明了将要在下面的章节使用的重要的技术性假设,因此需要你对它们有所了解。

[1] 我在本章这个介绍性的章节使用SQL和SQL风格的语法是因为大家都对它比较熟悉,尽管它并不是我喜欢的风格,并且(更重要的是)事实上它可能会让这个抛砖引玉的例子更不容易解释清楚。

[2] 在本书中我都使用未经认证的“键”这个词来表示候选键,而不是一个特定的主键。实际上,在第2章中就会介绍到的“Tutorial D”对于主键和其他键在语法上并没有区别。但是,由于大家使用习惯的原因,我在图例中都使用双下画线来表示主键属性,如图1.1所示。

[3] 我在前言的时候就声明过,在本书中我将使用《SQL and Relational Theory》作为简写来引用我的另一本书《SQL and Relational Theory: How to Write Accurate SQL Code》(第2版,奥莱利出版,2012)。

[4] 正是由于这个原因,更真实一点版本的视图LS很可能会去掉CITY属性。在这里我选择不这样做,是为了让这个例子更简单易懂。

[5] 曾经有一位审阅者问我,为什么我在这里要选择“补偿性操作”这个词。当时我自己认为这个答案很明显,不过为了以防万一,我还是在这里做一个说明:之所以我把这些操作称为“补偿性”的,是因为它们都会导致第2次更新发生来补偿第1次的效果(当然是大体上来讲)。

[6] 级联删除通常都被认为是使用一个特定的外键约束,但是补偿性操作的概念其实更加通用,并且适合很多种约束。

[7] 我假设有些用户“可能”会被允许进行这样的操作,如果他或她在操作被拒绝的时候能够接受错误信息显示的只是“因为系统就是这么说的”,而没有更进一步的解释。关于这一点的进一步讨论详见第4章。

[8] 在此感谢David McGoveran,他在多年前启发我思考这些问题。

[9] DBA=database administrator,也就是数据库管理员。


适合我的一切也应该能适合你。

——Walt Whitman《Leaves of Grass》(1885)

上一章的讨论是基于SQL的,因为大家对它都很熟悉。但实际上很可惜,SQL并不适合作为这种探究的基础,也无法满足目前手上课题对细节技术讨论的要求。一方面来讲,我们需要检验的概念很多时候完全无法用SQL语句表达;从另一方面来看,即使是可以表达的时候,SQL通常也会引入一大堆与之毫无关系而又没有必要的复杂内容,很容易使人一叶障目,不见森林。由于以上原因,在本文余下部分中,我将不会使用SQL作为论述基础(不过有些地方我还是要针对SQL做出一些说明),而是使用一种假想的语言,名字叫Tutorial D[1]。我相信现在这个语言已经广为人知了。不过,如果你需要一个比较综合的解释说明的话,可以在我和Hugh Darwen合著的《Databases, Types, and the Relational Model: The Third Manifesto》(第3版,Addison-Wesley出版,2006)[2]这本书中找到。

正如上面这本书的标题所写,后文中我会用“关于宣言的那本书”作为简称来引用,它还为大家介绍和解释了“第三宣言”,这是一个很精准的对于关系模型和支持类型理论(也包含了一个关于类型继承的复杂模型)的正式定义。在那本书中,我们使用“D”作为所有能够符合“宣言”原则要求语言的通用名。许多优秀的语言都够得上“D”的资格,很可惜,SQL是个例外。反观Tutorial D就是一个合格的“D”。实际上,Tutorial D原本就是被设计为一个说明和讲解“宣言”理念的合适载体,它也同样适合在本书中作为我研究的基础语言。因此,虽然我的说法是本书中的讨论要以Tutorial D为基础,而其实更准确的说法应该为这些讨论本质上是建立在“宣言”的理念基础上的。本章的其余部分包含一个关于这些理念想法的调查(当然,调查并不包含与我们的文章主题关系不大的那些观点)。换句话说,它主要是由一些你可能已经比较熟悉的内容组成的。即使如此,这一章仍然值得你至少“从头到尾马马虎虎地”读一遍,只要能对后面章节所倚重的概念和名词做些了解就可以。

每一个关系都有一个“关系头”和一个“关系体”,其中关系头是一系列状态和特征,而关系体则是符合关系头的一系列数组。让我们再次使用“供应商与零部件”数据库(如图2.1所示,与第1章的图1.1完全一样),供应商关系的关系头是{SNO CHAR, SNAME CHAR, STATUS INTEGER, CITY CHAR}的集合,我们假设在这里确定属性SNO、SNAME、STATUS和CITY分别被定义为CHAR、CHAR、INTEGER和CHAR这几种数据类型,而这个关系的关系体是对于供应商S1、S2、S3、S4和S5的数组的集合。这里要注意,每个数组都符合特定对应的关系头的要求,因为它们每个都只包含一个值,分别对应属性SNO、SNAME、STATUS和CITY(并且没有其他内容)。注意:其实更准确,也是更正确的说法应该是每个数组都“拥有”特定对应的关系头,因为在一个关系中,数组是应当有关系头的。

图2.1 供应商与零部件数据库的样本值

每个关系也都拥有(或者说属于)一个特定的“类型”,而这个特定的类型其实完全是由特定的关系头决定的。因此,我们在Tutorial D中表示一个给定的关系的类型时,其实就是关键词“RELATION”后面跟着适用的关系头。举例来说,供应商关系的类型就是:

RELATION { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }

接下来我要说,关系本身和关系变量之间存在着逻辑差异[3]。现在我们再来看一下图2.1。这张图呈现了3个关系,也就是说,这3个关系有可能在特定的时间里同时存在于数据库中。但是,如果我们换个时间再来看数据库的话,很有可能我们看到的就是其他的关系。换句话说,S、P和SP都是变量,准确地说是关系变量,它们跟其他变量一样,值会随时变化。那么既然它们是具体的关系变量,那么在给定的时间内,它们的值就是关系值。不过要注意,对于一个给定的关系变量来说,合法的值必须都属于同样的关系类型,例如,这些值必须都有相同的关系头,而这个关系类型和关系头也因此被看作这个关系变量的类型和头。

为了更全面地阐述前面提到的想法和理念,我们假设关系变量S拥有和图2.1所示相同的值,我们再假设现在要删除在伦敦的供应商的数组。

DELETE ( S WHERE CITY = ‘London’ ) FROM S ;

关系变量S现在应改变成如下的样子。

从概念上来讲,S原来的值(当然是个关系值)已经被一个新的值(另一个关系值)整体替换掉了。现在我们来看,原来的值(有5个数组)和新的值(有3个数组)似乎看起来很像,但是其实它们是完全不同的值。我们用“DELETE”作为刚才操作语句的缩写[4],实际上在这里它等价于下面的“关系赋值”。

S := S WHERE NOT ( CITY = ‘London’ ) ;

或者等价于:

S := S MINUS ( S WHERE CITY = ‘London’ ) ;

那么经过所有这些赋值操作会发生什么呢?我们会看到(a)在右侧的源表达式是需要运算的,而(b)运算得出的值就会被赋给等式左侧的目标变量,于是就会产生我们刚刚讲过的效果。

因此DELETE是一个特定关系赋值的缩写,当然,对于INSERT和UPDATE来说也是相似的,它们从本质上来说也是对于特定的关系赋值的一个缩写。从逻辑上来讲,实际上关系赋值是我们唯一真正需要的更新操作(这是我在下一节要重点说明的观点)。

综上所述:关系本身和关系变量之间存在着逻辑差异。也正因为如此,我在之后也会很谨慎地区分它们二者,当我用“关系值”这个词的时候我就是在指关系的值,而当我说“关系变量”这个词的时候指的就是关系的变量。不过,我大多数时间还是会用“关系”作为“关系值”的简称(就好像我们用“整数”来作为“整数值”的缩写一样)。并且我大多数时间会用“relvar(关系变量,译者注)”来作为“关系变量”的缩写,例如,我会说“供应商与零部件”数据库包含3个“关系变量”(更具体一点来说,是3个“真实”或基础关系变量,之所以这么说是为了把它们和“虚拟”关系变量或者视图区分开)。

基础关系变量定义

下面是Tutorial D对于目前例子中使用的3个关系变量的定义,以供后面参考引用。

VAR S BASE RELATION
  { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
  KEY { SNO } ;

VAR P BASE RELATION
  { PNO CHAR , PNAME CHAR , COLOR CHAR , WEIGHT RATIONAL , CITY CHAR }
  KEY { PNO } ;

VAR SP BASE RELATION
  { SNO CHAR , PNO CHAR , QTY INTEGER }
  KEY { SNO , PNO }
  FOREIGN KEY { SNO } REFERENCES S
  FOREIGN KEY { PNO } REFERENCES P ;


出于本书的目的,我选择忽略Tutorial D在目前定义中并不包含明确的FOREIGN KEY语法的这个事实。

Tutorial D中的关系赋值语法,不是像INSERT或DELETE一样的缩写,而是使用如下的通用格式。

R := rx

在这里“R”是一个关系变量(从语法上来说,其实就是一个关系变量的名字),“rx”是一个关系表达式,表示关系“r”和关系变量“R”是同一类型的。现在显而易见的是(这里要感谢David McGoveran的观察)任何这类赋值都在逻辑上等价于下列格式之一。

R := ( r MINUS d ) UNION i

在这里:

以上几点可以很方便地用文氏图来解释清楚,如图2.2所示。解释说明:在这个图表中r、d和i如上面所述,u则是与它们同类型的全域关系(换句话说,u是由所有与R具有相同关系头的数组所组成的关系,当然也包含那些已经在R之内的数组)。请注意u-r的差集是r的绝对补集(也就是说,u-r是由所有与R具有相同关系头,但不包含在R之内的数组所组成的关系)。

图2.2 删除集合d和插入集合i

当然,删除集合d有可能为空,在这种情况下,之前的赋值效果就相当于一个纯粹的INSERT操作。或者当插入集合i为空时,赋值效果就相当于一个纯粹的DELETE操作。又或者,d和i都为空,那么赋值操作的效果就会退化为一个“空操作”R := R

根据以上所说的,原来的赋值R: = rx实际上等价于下面的“多重”赋值(“多重赋值”详见本节后面的详细介绍)[7]

DELETE d FROM R , INSERT i INTO R

如此一来,虽然我前面曾经说过这类关系赋值其实是我们唯一需要的更新操作,但是我们还是可以始终把它想成DELETE和INSERT这两个操作(从感觉上来讲,好像还是这样想起来方便一些)。这是因为当我们遇到一个很可能发生的随机的针对关系变量R的多重赋值时,包括前面例子中这种用公式表达的比较清晰的对R的UPDATE操作,我还是建议将赋值转化为一个对R的DELETE操作和一个对R的INSERT操作,毕竟删除集合d和插入集合i是明确定义的、交集为空的,和独一无二的。注意:由于这里已经讲过了比较清晰的UPDATE操作,所以接下来我只有在遇到值得说的地方才会再次讨论这个问题。同样,我也会忽略d和i在实际运算中所碰到的大多数问题。和我其他大多数作品一样,在本书中我首要关注的是先要让大家明白这个理论,而不是首先关注实际执行中出现的问题。当然,我并不是说实际操作当中遇到的问题不重要。Au contraire(法语:恰恰相反),实际上检查实施操作的可行性对确保本理论的正确性是至关重要的。

语法要点

我在这里再次重申,关系赋值R: = rx在逻辑上等价于:

DELETE d FROM R , INSERT i INTO R

请一定注意,正是因为d和i的交集为空,所以无所谓DELETE或者INSERT哪个操作“先进行”。(关于这个问题我一会儿还要再多说一点,即在多重赋值的情况下独立操作的完成情况。)所以,我们等于可以这么说:原始赋值逻辑上与下面任意一个关系表达式等价。

WITH ( R := R MINUS d ) : INSERT i INTO R

WITH ( R := R UNION i ) : DELETE d FROM R

我们甚至可以说它也和下面任意一个关系表达式等价。

WITH ( DELETE d FROM R ) : INSERT i INTO R

WITH ( INSERT i INTO R ) : DELETE d FROM R


如果你不熟悉上面所使用的WITH操作的话,我推荐你看看《SQL and Relational Theory》。

其实还有更棒的。Tutorial D还额外支持DELETE和INSERT的变体,分别叫作I_DELETE(“既有值DELETE”)和D_INSERT(“无交集INSERT”)。使用I_DELETE,会在你试图删除一个不存在的数组(也就是最开始没有出现的数组)时报错;类似的,使用D_INSERT会在你试图插入一个已经存在的数组(也就是已经出现的数组)时报错。因此,我们可以说原赋值R: = rx逻辑上等价于以下任意关系表达式。

I_DELETE d FROM R , D_INSERT i INTO R

D_INSERT i INTO R , I_DELETE d FROM R

为了简化问题让大家易于理解,在本书之后的部分我还是会以传统的DELETE和INSERT操作作为例子进行讲解,并且我还会在全文中应用下面这个假设,请一定注意!尝试删除不存在的数组不是错误,尝试插入存在的数组也不是错误。

多重赋值

“第三宣言”需要的,也是我们已经提过的Tutorial D肯定支持的,是一种多重赋值的方式,这种方式允许许多彼此独立的赋值操作“同时进行”。例如:

DELETE ( S WHERE SNO = ‘S1’ ) FROM S ,
DELETE ( SP WHERE SNO = ‘S1’ ) FROM SP ;

解释:首先,请注意第一行末尾的逗号,它意味着两个DELETE都是同一个整体声明的一部分。第二,如我们所知,DELETE其实就是赋值,所以上面这个“双DELETE”其实就是下面这个双赋值的缩写。

S := ... , SP := ... ;

这个声明分别给关系变量S和SP各赋了一个值,它们都是同一个操作的一部分。我们概括来讲,多重赋值的语义有下面2条。

我们来观察一下,正是因为所有的源表达式都要先进行计算,然后独立赋值再执行,所以那些独立赋值都不会被其他运算结果影响,也因此它们被执行的顺序也跟运算结果无关(如果愿意,你甚至可以把它们的执行想象成平行发生的,或“同时进行”的)。另外,多重赋值从语义上来讲被当作“原子级”操作,也就是说在这类赋值发生的“过程中”不会进行任何一致性检查(这也正是为什么“宣言”从开始就要求对操作的支持)。注意:一致性约束在本章后面会有详细讨论。

语义不是语法

我前面已经说过,任何一个关系赋值都可以等价为一个DELETE操作加上一个INSERT操作,当删除集合d和插入集合i是被准确定义,交集为空,并且独一无二时。但是请注意下面这个重点,两个不同的赋值可以使用完全不同的语法,但却对应相同的删除集合和插入集合,下面我为大家详细说明(在这里要感谢Hugh Darwen提供了这个例子)。

假设一个关系变量R只有两个属性,K和A。我们令{K}为单键,然后,令K与A的类型都是INTEGER,并令R只包含两个数组,(1,2)和(3,−2)。[9]现在我们来看下面两个显式的UPDATE操作。

UPDATE R : { K := K + A } ;

UPDATE R : { A := -A } ;

请注意,具体来说第一个操作是“键UPDATE”,而第2个则不是,因此,如果一个格式,如ON UPDATE K…的补偿操作已经被定义(在SQL环境下很可能发生),那么我们可以预测这个操作会与第一个UPDATE有关,而与第2个无关。但是很容易看出,如果我们给R设定具体的初始值,那么这两个UPDATE的效果完全等价。实际上,它们都等价于下面这个赋值。[10]

R := RELATION { TUPLE { K 1 , A -2 } , TUPLE { K 3 , A 2 } } ;

换句话说,删除集合d对这两个UPDATE而言是完全一样的,而插入集合i也是如此。(练习:这两个集合它们到底是什么?)因此很明显,我们想要的是对于补偿操作而言,它应当是由相应的删除集合和插入集合适时地调用,而不是由碰巧列出更新表达式的语法武断地选择。

每个关系变量都受制于一系列一致性约束,我们也可以把它们简称为约束。首先,我们在“关系和关系变量”这一节中已经知道,任何一个给定的关系变量都被约束为一个确定的类型(具体来说,是一个确定的关系类型),也就是说,在定义关系变量的时候,这个类型也就确定了。例如,我们再来看看供应商关系变量S的定义。[11]

VAR S BASE RELATION
  { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
    KEY { SNO } ;

如你所见,这个定义清晰地表明了关系变量S属于类型RELATION {SNO CHAR, SNAME CHAR, STATUS INTEGER, CITY CHAR}。而且,具体来看它还表明了属性SNO、SNAME、STATUS和CITY分别是CHAR、CHAR、INTEGER和CHAR这几种数据类型。注意:后一种约束——给每个独立属性的约束,有时候被称为“属性”约束,本段就是一个很好的例子。

我再次重申一遍,任何一个关系变量(任何一个属性更是如此)都被约束为某种类型。但是关系变量同时也会被很多其他的约束所限制,这些约束通常都是使用Tutorial D的表达式明确写出CONSTRAINT声明从而实现的。[12]下面举几个简单的例子希望能帮助大家理解(如果你需要更多的解释说明,请参阅《SQL and Relational Theory》)。

CONSTRAINT CX1 IS_EMPTY ( S WHERE STATUS < 1 ) ;

CONSTRAINT CX2 IS_EMPTY ( S WHERE CITY = ‘London’ AND STATUS ≠ 20 ) ;

CONSTRAINT CX3 IS_EMPTY ( ( S JOIN SP ) WHERE STATUS < 20 AND PNO = ‘P6’ ) ;

现在“第三宣言”中要求所有的约束都必须在声明边界上被满足(“立即检查”)。换句话说,从逻辑上讲,对于任何有潜在可能会违反“宣言”的声明,在它约束时都会被立刻重新检查一次。[13]或者我们也可以说得有趣一点,它们是“在分号处”被检查的。因此,与SQL标准版本或其他特定的SQL产品不同,一致性检查从来都不会被推迟到执行结束或者执行COMMIT(提交)操作的时候。

最后请注意,有时为了方便,我们对于给定的关系变量R的“全局”约束,这里要强调是针对“关系变量”的全局约束,指的是所有涉及到关系变量R的约束的逻辑AND(与)。而有时为了方便,我们对于给定的数据库的“全局”约束,这里要强调是针对“数据库”的全局约束,指的是所有涉及到该数据库中任意关系变量的约束的逻辑AND(与)。

更新每次都是集合更新

尽管这个观点尽人皆知,但还是值得我们再次强调,在关系模型中的每次更新都是集合更新(更好的说法是:每次都是“关系”更新)。我们换句通俗点的话来说,INSERT操作给目标关系变量插入了一系列数组,DELETE操作将一系列数组从目标关系变量中删除。而更广义地来讲,关系赋值将一系列数组值赋给了目标关系变量。当然,我们经常会这么说(例如)“插入某某数组”,确实,我在这本书里也会时不时这样说,但是这种说法确实是很不严谨(虽然比较方便)的。不论如何,这里我要说的重点在于一致性约束的检查要在“所有的”更新步骤(包括与之相关的补偿性操作,如果有的话)全部完成后进行。换句话说也就是从逻辑上讲,一个集合级别的更新一定不能被当作一系列单独数组级别的更新来对待。

两个重要的原则

现在我要在此特别强调2个重要的原则,这2个原则对于巩固更新(尤其包括视图更新)的概念非常重要。第1条原则也被称为黄金法则

显然,由此条法则我们可以立刻推导出下面的规则:所有的关系变量都不能违反它自己的全局关系变量约束。简单来讲,就如同我所强调过的,约束必须在声明边界上被满足。我再次重申,请尤其注意,这条法则无论是对视图,还是对基础关系变量,都同样适用。

第2条原则被称为“赋值原则”。

这条原则适用于所有的赋值操作,实际上,相信你也察觉到了,它基本上就是赋值本身的“定义”,不过就本文而言,它尤其能够适用于关系赋值。当然也请再一次注意,这条法则无论是对视图,还是对基础关系变量,都同样适用。

在第1章中,当我介绍抛砖引玉的例子(供应商与零部件数据库)时,曾提到了这样一件事:

表S的内容列出了签过合同的供应商。每个供应商有一个唯一的供应商编号SNO、可能不唯一的供应商名字SNAME(尽管图1.1中的样本值恰好是唯一的),以及状态值STATUS和位置CITY。

当然,在第1章中的“表S”似乎应该叫作“关系变量S”更加准确,不过这并不是重点。重点在于:第一,目前的文字代表了关系变量S的意义,并且更容易被用户理解;第二,这种意义的措辞实际上是“谓词”(有时候也说“解释”或者“预期解释”,但在本书中我还是主要使用“谓词”这种说法)。换句话来讲,可以说每一个关系变量都有一个相对应的谓词,我们称之为这个关系变量的“关系变量谓词”,而关系变量谓词的本质是用户对与之对应的关系变量的理解。下面我们来举个例子,这里我选择声明关系变量S的谓词。

S:供应商SNO是已经签约的,名字为SNAME,有状态STATUS,位置在CITY城市。

下面是关系变量P和SP的谓词。

P:零部件PNO在公司内使用,名字为PNAME,有COLOR颜色和WEIGHT重量,并且存储在CITY城市。

SP:供应商SNO供应零部件PNO,数量为QTY。

作者注:[14]也许我应该对我在本书中所使用的“谓词”这个词语多做一些说明。首先一点,在SQL中这个词被广泛地用来表示布尔值或真值表达式(比如比较谓词、IN谓词、EXISTS谓词等),所以你可能对它已经很熟悉了。但是,虽然这种用法在SQL上并不是完全错误的,但它确实滥用了一个非常通用的词汇,这个词汇在数据库环境中非常重要,并且给了它一个特别具体的意思,这也是为什么我决定不沿用这种用法的原因。

第二,为了信息准确我应该来解释一下,谓词并不是像上面我们写的,就是自然语言中的几个句子。相反,谓词是由这些句子所决定的。换个说法就是关系变量S的谓词该是什么就是什么,不管你是用英语或者西班牙语,还是其他任何语言来表达,它的核心内容都是一样的。但是在后面的文章中,为了表达简明,我会假设谓词就是1句话,一个(通常)用自然语言表达的句子。(类似的标注也会用到命题上,看下面。)

最后,我已经解释了这个词的意思,尽管有前面一段的解释,但是你还是需要知道,对于谓词“到底”是什么,即使逻辑学家们也很难有非常一致的答案。尤其是有些作者认为谓词是一个单纯的概念,它本身并无意义,而认为我上面所写的应当是预期解释,而不是谓词。我不想在这里对这些事情做过多的争辩。如果想看更多的讨论,我推荐你去看“What’s a Predicate?(什么是谓词)”这篇文章,你可以在Hugh Darwen和我合著的著作《Database Explorations: Essays on The Third Manifesto and Related Topics》(Trafford,2010)中找到它。

你可以把谓词想成是一个“真值函数”。和所有的函数一样,它也有一系列参数。当它被调用的时候会返回一个结果,而(因为是真值函数,所以)结果是TURE或者FALSE。那么在关系变量S的谓词这个例子中,这些参数就是SNO、SNAME、STATUS和CITY(对应的显然是这个变量的属性),它们的值分别是相应的类型(分别是CHAR、CHAR、INTEGER和CHAR)。当我们引用调用该函数时,逻辑学家们会说当我们“实例化谓词”时,我们把一组确定的值代入参数。假设我们代入的值分别是S1、Smith、20和London。那么可得:

供应商S1签约了,名字是Smith,状态是20,位于城市London。

这句话其实是一个“命题”,在逻辑上它并没有参数,也无法得出一个明确的“ture”或“false”的结果。(当然对于手中这个例子,命题为真,或者至少我们这样假设。)这把我们引入另一个非常重要的原则中,它就是闭域假设(The Closed World Assumption)。

举个例子,我们再次参看图2.1,数组(S1,P1,300)出现在关系变量SP中,因此供应商S1可以提供P1零部件,数量300 就成为一个“真正的事实”。但是如果我们从该变量中删除该数组,那么我们就会说(实际上)供应商S1可以提供P1零部件,数量300“不再是个事实”。

到目前为止,我们讨论了谓词和关系变量的具体关系。不过,实际上目前所有的概念都自然而然地向关系型表达式延伸。例如,考虑到供应商在除了CITY以外的所有属性上都有投影(注意Tutorial D对投影的语法)。[16]

S { SNO , SNAME , STATUS }

这个表达式表明一个关系包含所有形如(s,n,t)的数组,而一个形如(s,n,t,c)的数组现在出现在关系变量S中,CITY值是c。换句话说,就是结果包含且仅包含所有对应下面谓词中真值实例的数组。

这里存在着这样的城市CITY,供应商SNO已签约,名字是SNAME,状态为STATUS,并且位于城市CITY。

这个谓词代表了一个表达式——一个关系表达式S{SNO,SNAME,STATUS}。观察这个表达式你会发现,这里有3个参数,而对应的关系本身有3个属性。CITY 并不是该谓词的参数,而是被逻辑学家们称为“约束变量”,因为实际上它被“这里存在着这样的城市CITY”这句话所“量化”了。注意:这里可能有一个更清楚的方式来说明这点(也就是,谓词有3个参数,而不是4个),那就是这个谓词效果上等价于下面这一个。

供应商SNO已签约,名字是SNAME,有状态STATUS,并且位于某处(意思是它位于某个城市,但是我们不知道具体是哪一座城市)。

注意类似的方式可以应用到任意可能的关系表达式中。具体来说就是:任意关系表达式rx总是有一个对应的意义,或谓词;而且rx的谓词总是可以被表达式中的关系变量的谓词决定,与表达式中关系操作的语义保持一致。[17]

作者注:再次重申,除了CITY之外,所有供应商的属性的投影都可以用Tutorial D表示为形式S {SNO,SNAME,STATUS}。而为了更加方便地表达,Tutorial D也允许在表达式中利用被移除的属性代替被保留的属性进行表达。因此,以上面的表达式为例,它和表达式S {ALL BUT CITY}是等价的。在适当的情况下,在Tutorial D范围内类似的情况可以应用于所有的操作。

就我目前为止对关系变量谓词的描述来看,好像它们很不正规,实际上它们只是在自然语言中对关系变量拥有特定的属性(当然,是特定的类型)这一事实的陈述和表达。不过我们也可以使用更加正式一点的表达方式。例如,给定供应商关系变量S有属性SNO、SNAME、STATUS和CITY,并且给定{SNO}是该关系变量的单键,那么我们说相应的谓词应该如下面这样。

is_entity  ( SNO ) AND
has_SNAME  ( SNO , SNAME ) AND
has_STATUS ( SNO , STATUS ) AND
has_CITY   ( SNO , CITY )

假设数组t出现在关系变量S。那么这个谓词表达的意思是:

当然,系统并不知道这个实体,即被SNO确定的实体,其实就是现实世界中的一个供应商,实际上是一个“已签约”的供应商,也不知道它的意思是拥有SNAME属性、STATUS属性或CITY属性。

类似地,我们也可以说除了CITY之外所有供应商属性的投影的谓词为:

is_entity  ( SNO ) AND
has_SNAME  ( SNO , SNAME ) AND
has_STATUS ( SNO , STATUS ) AND
EXISTS CITY ( has_CITY ( SNO , CITY ) )

最后,在结束本节的时候我要向大家坦白,到目前为止我所讲的所有关于关系变量谓词的内容都故意地进行了简化。不过当然,我相信这样略有简化的内容对于做介绍来说是足够的了。而对于更详细的内容——“真实的故事”,或者至少是比现在的“真实一点”的,我建议你参看我的另一本书《Logic and Databases: The Roots of Relational Theory》(Trafford,2007)第4章(“闭域假设”)。

就本书的主体部分而言,我当然会讨论一些关系代数的运算符(及其代表的操作)的细节,而基本上我会认为你们理所当然已经对这些运算符及其操作(如果你需要,可以在附录B中找到它们的定义)很熟悉了。但是这里有一些我经常提及的特定操作你们可能并不熟悉;它们被Tutorial D所支持并且很有用处,但是在大多数常见的教材中却并未被讨论过(可能因为它们并不能直接被SQL支持)。第一个我们要说的就是MATCHING,下面是它的定义。

这里有一个例子(“求得最近供应过至少一个零部件的供应商”)。

S MATCHING SP

给定我们经常使用的样本值,那么结果应该像下面这样:

Tutorial D同时也支持一个“非”格式的操作NOT MATCHING。下面是它的定义。

这里有一个例子(“求得最近没有供应任何零部件的供应商”)。

S NOT MATCHING SP

给定我们经常使用的样本值,那么结果应该像下面这样:

另外一个我想给大家介绍的操作是EXTEND。EXTEND有2种形式。第1种形式的定义如下。

那么现在我们来看,假设关系P中零部件的重量一般使用磅为单位,现在我们想用克为单位来显示这些重量信息。克与磅的换算关系是454克为1磅,所以我们可以这么写:

E XTEND P : { GMWT := WEIGHT * 454 }

给定我们经常使用的样本值,那么结果应该像下面这样:

EXTEND的第2种形式主要瞄准的是“假设情况”查询。换句话说,它可以直接浏览特定变化带来的效果,而不需要对目标对象做出实际变化(可能然后再撤消)来实现。这里有个例子(“要是零部件的重量单位使用的是‘克’而不是‘磅’会怎么样?”)。

EXTEND P : { WEIGHT := WEIGHT * 454 }

这里的重点在于在大括号中的赋值目标属性并不像上一个例子中那样是一个“新”的属性,而是一个已经存在于特定关系(在这里这个关系就是关系变量P目前的值)中的属性。整体来讲,这个表达式被定义成与下面所列出的表达式等价。

( ( EXTEND P : { GMWT := WEIGHT * 454 } ) { ALL BUT WEIGHT } )
                                            RENAME { GMWT AS WEIGHT }


正如你所见,这个展开式中使用了Tutorial D RENAME操作。我想这个操作已经差不多人尽皆知了,当然如果你需要的话还是可以在附录B中找到相应的定义。更多关于它的讨论请参阅《SQL and Relational Theory》。

让我们用第2种形式的定义来作为本节的结尾。

这是本章的最后一节。在本节中,我希望你们能注意一个重要的事实,正如关系值与关系变量(relvars)之间有逻辑区别一样,数据库值和数据库变量(dbvars)之间也存在着逻辑差异。下面一段引自《宣言》那本书。

第 1 版的“宣言”将数据库的本质(也就是数据库的值)与数据库变量进行了区分……[同时也]指出未明确定义的词汇“数据库”应该就是明确地代表数据库的值,并介绍了作为“数据库变量”的缩写来使用的词汇“dbvar”。虽然我们依然相信这个区别本身是真实有效的,但随即我们发现其实它与“宣言”所要讲的其他方面内容几乎没什么关系。因此我们决定,为了文章的易读性,还是在文中使用传统的说法。[19]

于是,当我们“更新某些关系变量”(在某个数据库内部)时,实际上我们更新的是相应的数据库变量。(为了表述清晰,在本节的其余部分我都将使用数据库变量这个词。)例如,Tutorial D声明

DELETE ( SP WHERE QTY < 150 ) FROM SP ;

“更新设备供应关系变量SP”其实是更新了整个供应商-零部件数据库变量——对这个数据库变量的“新的”数据库值和“老的”值基本一样,除了特定的“设备供应”数组被移除了。换句话说,有时候我们会说一个数据库“包含变量”,即相应的关系变量,其实这种说法很笼统,而且很不正式。更加正式的并且更准确的描述方式应该是这样的:

数据库变量就是数组变量。

数组变量对该数据库变量中的每一个关系变量都拥有一个唯一的属性(并且没有其他属性),并且每个属性都有关系值。我们举例来说,在供应商-零部件数据库中,我们可以将整个数据库变量想象成一个数组变量(也可以用“tuplevar”表示),数组类型如下:

TUPLE { S RELATION { SNO CHAR , SNAME CHAR ,
                     STATUS INTEGER, CITY CHAR } ,
        P RELATION { PNO CHAR , PNAME CHAR ,
                     COLOR CHAR , WEIGHT RATIONAL , CITY CHAR } ,
        SP RELATION { SNO CHAR , PNO CHAR , QTY INTEGER } }

让我们暂时称供应商-零部件数据库变量(或者也可以叫它数组变量)为SPDB。[20]那么上面我们刚刚看到过的DELETE声明可以作为下面的“数据库”(以及数组)赋值。

SPDB := TUPLE { S ( S FROM SPDB ) ,
                P ( P FROM SPDB ) , 
                SP ( ( SP FROM SPDB ) WHERE NOT ( QTY < 150 ) ) } ;

解释说明:这个赋值右侧的表达式表示一个数组拥有3个属性分别叫作S、P以及SP,每一个属性都拥有关系值。[21]在数组内,属性S的值就是当前关系变量S的值,属性P的值就是当前关系变量P的值,而属性SP的值则是在当前关系变量SP的值的基础上,减去那些数量小于150的数组。

我们来看另一个例子,这里我们再次使用“双删除(DELETE)”,我在本章早些时候介绍多重赋值概念的时候曾经使用过。

DELETE ( S WHERE SNO = 'S1' ) FROM S ,
DELETE ( SP WHERE SNO = 'S1' ) FROM SP ;

现在我们观察这个关系赋值,很明显是个多重赋值,会发现它逻辑上等价于下面的“单一”数据库(以及数组)赋值。

SPDB := TUPLE { S   ( ( S FROM SPDB ) WHERE NOT ( SNO = 'S1' ) ,
                P   ( P FROM SPDB ) ,
                SP  ( ( SP FROM SPDB ) WHERE NOT ( SNO = 'S1' ) ) } ;

换句话说,目标变量全部都是同一个数据库(或者说同一个数据库变量)中的关系变量,多重赋值其实仅仅就是一个语法工具,它可以让我们将逻辑上的“数据库”赋值表达成一个一系列独立“关系”赋值的组合(请参看下面关于此点更多的讨论)。而一个“单一”的关系赋值,例如由只针对一个单独的数据库变量进行的一个单独的赋值所组成的“多重”赋值——仅仅是个特例。从根本上来说,事实上目前对数据库更新来讲,数据库变量是唯一真正的变量。这就是说,数据库更新其实“总是”对某些数据库变量进行更新,而对单一一个数据库关系变量的更新仅仅是特例而已。

根据上面所有的内容,如果目标关系变量是一个数据库关系变量(就是说,如果这个目标关系变量是某个数据库中的关系变量),那么即使一个“单一”关系赋值其实也是一个数据库赋值。当然,这个数据库赋值像所有的赋值一样,都必须遵循“赋值原则”,并且正由于它是一个数据库赋值,它也同样需要遵循“黄金法则”。

我们前面讲到的这些想法太重要了,它值得我们再次复习一遍,只是说法有轻微的不同。首先,数据库变量是数组变量,数据库(也就是在给定的时间,给定的数据库变量所具有的值)是所有属性都有关系值的数组。第二,给定一个关系赋值如下面格式。

R := rx

(这里R是一个关系变量参考量,从语法上讲就是一个关系变量的名称,指出了一些数据库关系变量,而rx是一个关系表达式,表示关系r与R具有相同类型)这个关系变量参考量R其实是一个“伪变量”参考量(请立刻参看本段下面的注解)。换句话说,关系赋值其实是针对相对应数据库变量(再次重申它就是数组变量)的一个部分进行“替换”的简称化表达方式。因此我们推断出“关系变量”(至少是在数据库中的关系变量)并不是真正的变量,它们只是一些让人觉得很便利的虚构的变量,能够制造出这种假象:数据库或数据库变量可以一点一点地、一个关系变量一个关系变量地被更新。我们进一步可以推断出,在某种意义上,“关系赋值”(不管是不是多重的)也同样是个方便的假象,具体来说就是它是一个虚拟操作,让我们感觉好像可以通过一系列针对这个数据库内的单独的关系变量的更新,来实现对数据库变量的更新。[22]

关于伪变量的注解:从本质上讲,伪变量是这样一个概念,或者是这个概念的象征性的名字,它看起来像是个可操作的表达式,但不同的是它并不表示任何值;相反地,它出现在某些赋值中的目标位置,来表示“变量的一部分”。我们举个例子,令X为一个CHAR类型的变量,并令X目前的值是“antimony”。那么赋值表达式SUBSTR(X,5,3) := ‘nom’的效果就是“替换”X的第5~7个字符,将“mon”替换成“nom”,并因此将X的“旧的”值(“antimony”)完全替换成“新的”值(“antinomy”)。这个赋值左侧的SUBSTR(X,5,3)就是一个伪变量参考量,而这个赋值本身则是下面表达式的简化版:X := SUBSTRX(X,1,4) || ‘nom’ || SUBSTR(X,8,1),在这里符号“||”表示字符串连接。注解完毕。

为了本书的易读性和定义的明确性,虽然有违我的最佳判断,但我还是决定在本书余下的部分不再使用数据库变量和“dbvars”这两个专用术语,至少不会用很多次,而是使用大家熟悉的“数据库”,希望我要表达的意思在上下文中总是很清楚。读者要擦亮眼睛。

[1] 说这个语言是假设的,仅仅是因为到我撰写本书的时间为止,市场上还没有一个正式发售的商业版本。但是它的设计原型已经存在,可以通过下面的网址来访问:www.thethirdmanifesto.com (请参看下面紧跟着的另一个注脚)。

[2]  Tutorial D从该书第1次出版以来已经被修订和扩展。关于新版本(已经有改善,但和本书中使用的并不完全相同)的描述可以在我和Hugh Darwen合著的《Database Explorations: Essays on The Third Manifesto and Related Topics》(Trafford, 2010)中找到,同样可以访问网站www.thethirdmanifesto.com来获得相关资讯(这本书同时还有很多其他关于“宣言”的内容)。

[3] “逻辑差异”这个概念,是我和Darwen在撰写技术文章时很爱使用的一个概念(特别是在关于《The Third Manifesto》的文章中),它来源于Wittgenstein(英国哲学家、数理逻辑学家维特根斯坦)的一句名言:All logical differences are big differences(所有逻辑上的差异都是巨大差异)。

[4] “缩写”在这里用引号括起来是因为其实它要比需要缩写的部分还长。但是一部分原因其实应该归咎于我使用的是不规范的Tutorial D术语(在真正的Tutorial D语法中,DELETE语句应该写成DELETE S WHERE CITY = ‘London’)。在本书中我特意选择了稍微啰嗦一点的语法来表达DELETE(和INSERT),这也是为了让它们的语义足够清晰。

[5] 严格来说,d和i当然不仅仅是“数组的集合”,而是和r同一类型的关系。

[6] 请参看附录A中对此命题的证明。

[7] 真正在Tutorial D中对于多重赋值的语句应当是DELETE R d, INSERT R i。

[8] 如果遇到两个或更多独立赋值拥有相同的目标变量,那么这个定义就需要被改进,不过目前我们还用不到这些。可以参看附件A来获得更多解释说明。

[9] 我将在这里和全书其他地方都使用这种简单的表示法来表示数组,以便大家阅读。

[10] 这个赋值在等号右侧的表达式是一个“关系选择器调用”,其实就是一个关系“字面量”。如果你想获得对相关内容的详细解释,请参考《SQL and Relational Theory》。

[11] 关系变量S是个基础变量,这是肯定的。关于视图的情况,请见第3章。

[12] 这里也有例外,有些约束可以用KEY和FOREIGN KEY的方式来表达,但即使是这些约束本质上也仅仅是约束的缩写形式,它们都可以使用CONSTRAINT清晰地进行声明,只不过会比较冗长。请参看《SQL and Relational Theory》一书来获得关于这一点更深入的讨论信息。

[13] 我在这里简单一点说:约束的效果很强大,一些关系选择器调用确实会让所需类型的关系检查得“更迅速”,甚至把检查作为得出关系选择器调用结果的流程中的一环。同样,请参看《SQL and Relational Theory》一书来获得关于这一点更深入的讨论信息。

[14] 这段注文是从《Database Design and Relational Theory》中截取过来的,我进行了少量编辑。注意:我曾经在前言就提醒过你,在本书中我会使用《Database Design and Relational Theory》作为缩写的形式来表示引用我的《Database Design and Relational Theory: Normal Forms and All That Jazz》(奥莱利,2012)。

[15] 我想强调“当且仅当”。这里至少有一个重要的推论。具体来说就是令关系变量R1和R2分别拥有谓词P1和P2,那么当P1和P2同时被同一个数组t满足时,t必须同时出现在R1和R2中。(虽然这是一种经验法则,但它可能是一个很好的方式来确保P1和P2足够特殊以排除前面的可能性。但是请注意,在第4章以及第9~11章我们将看到一些蓄意违反这个原则的情况出现。)

[16] 这里我要说明,我们发现一件很便利的事情,就是在Tutorial D的所有操作中,可以把最高优先级设给投影。

[17] 因此,我们可以将闭域假设加以扩展。在一个给定的时间内,我们设一个命题p在对应数组t并且在数组t满足下列条件时为真:(a)数组t在该时间(前面所说的给定的时间)内出现在某个数据库的关系变量中,或者(b)数组t在该时间内出现在某个关系中,并且可以由该关系推导出数组t就是这个数据库关系变量的值。我们换一句简单点的话来说,就是所有由数据库声明或说明的均为真,其他均为假。

[18] 我做这个标记是为了说明著名的关系差操作——Tutorial D中的MINUS是NOT MATCHING的一个特例。具体来说就是如果关系r1和r2是相同类型,那么r1 NOT MATCHING r2会简化为r1 MINUS r2,这一点可以很容易确认。因此,相对来说NOT MATCHIG比MINUS更加基础。

[19] 换句话说,我们继续使用“数据库”这个词,时而表示数据库变量,时而表示数据库值,并且我们压根不使用database variable或者dbvar这两个词。但是后来我们发现,因为种种原因我们没有设计这部分是一个非常错误的决定。(做一些你知道的逻辑上错误的事情通常不是个好主意。)至于我在本书中将计划如何来使用这个术语,请参看本节剩余的部分。

[20] 如果我们想要像这个例子中一样写出清晰的数据库赋值,那么数据库变量就必须拥有用户可见的名字,但在Tutorial D中它们没有(至少按照目前的定义来说)。

[21] 实际上,上面这个表达式是一个数组选择器调用。再次说明,如果你想获得关于这个的更多说明,请参看《SQL and Relational Theory》。

[22] 从这个讨论中我们可以看出多重关系赋值是极其重要的(在缺少明确的对数据库赋值的支持的情况下),实际上它对于后面章节要讨论的一些特定的视图更新规则来说非常的关键。因此在这种情况下,我要说,请原谅我在这里指出,很不幸,至少SQL不支持这个操作。


相关图书

数据模型记分卡
数据模型记分卡
图数据库(第2版)
图数据库(第2版)
你不可不知的关系数据库理论
你不可不知的关系数据库理论
图数据库
图数据库

相关文章

相关课程