本节书摘来华章计算机《SQL与关系数据库理论——如何编写健壮的SQL代码》一书中的第3章 ,第3.1节 C. J. Date 著 单世民 何英昊 许侃 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.1 元组是什么
下面这个是元组么?
不,它不是元组,它是一个元组的图示,但不是元组(本章唯一一次把类型名称和属性名称一起放到图示中)。在第1章中我们看到,一个“事物”和“事物的图示”是有逻辑区分(logical difference)的,而这个区分非常重要。比如,元组对于其属性是没有自左至右排序的,所以下面的图示对于刚才那个元组是同样好(或者同样坏?)的。
因此,尽管我在后面会使用很多与这些图示相似的图示,但要记住它们只是“图示”而已,而它们有时可能会给出错误的暗示。
在此告诫之后,现在给出元组精确的定义如下:
定义 设T1, T2, ? Tn (n ≥ 0)为可以同名的类型名称。与每个类型Ti对应,存在相互区别的属性名Ai;形成的n个属性名/类型名组合均为属性(attribute)。对应于每个属性,存在类型为Ti的属性值(attribute value)vi与其关联;产生的n个属性/值组合均为一个分量(component)。则,依此定义的所有n个分量的集合(设为t),即是涵盖属性 A1, A2, …, An的一个元组值(tuple value)(或简称为元组)。值n为t的度(degree);度为1的元组为一元元组(unary),度为2的元组为二元元组(binary),度为3的元组为三元元组(ternary),…。更一般的,度为n的元组为n元元组(n-ary)。由所有n个属性构成的集合为t的标题(heading)。
以上文中对应S1供应商的两个图为例,对于第一张图有:
- 度为4。对于标题也可说其度为4。
- 类型名(如图所示,自左到右)包括CHAR 、CHAR 、INTEGER、CHAR。
- 对应的属性名称包括SNO 、SNAME、STATUS、CITY。
- 对应的属性值为'S1'、'Smith '、20、'London'。注意此处用来封闭字符串的引号;我在图中并没有使用这样的引号,但我或许应该这么做,因为这样更为正确。
- 标题:这里最简单的就是显示另一幅图示:
显然,这幅图示表示一个集合,并且属性的顺序是任意的。下面是相同标题的另一图示:
练习:我们可以画多少个本质上一样的不同图示来表示这个标题?(答案:4!=4×3×2×1 = 24.)注1
一个元组是一个值;因此,和所有的值一样,元组也有一个类型(第2章中已介绍过),而此类型像所有的类型一样也有名称。在Tutorial D中,这样的名称采用tuple {H}的形式,其中{H}为标题。在我们的示例中,元组类型的名称为(其中的属性顺序是任意的):
TUPLE { SNO CHAR , SNAME CHAR , STATUS INTEGER , CITY CHAR }
再说一遍,一个元组是一个值。因此,元组像所有的值一样也必须由某个选择器调用返回(如果返回值是元组,自然应该是一个元组选择器调用)。下面是针对示例的一个元组选择器调用(还是用Tutorial D,分量的排序也是任意的):
TUPLE { SNO 'S1' , SNAME 'Smith' , STATUS 20 , CITY 'London' }
注意,在Tutorial D中,每个分量都指定为“属性名/表达式”对。其中,指定的表达式表示对应的属性值;省略了属性类型,因为它总是可以从指定表达式的类型推导得到。
另一个示例如下(不同于前例,此例中不是字面值,因为不是所有的参数都设定为文字):
TUPLE { SNO SX , SNAME 'James' , STATUS TX , CITY CX }
此处假定SX、TX和CX分别为CHAR、INTEGER 和CHAR 类型变量。
如前面示例所示,Tutorial D中的元组选择器调用通常由关键字TUPLE及由大括号封闭的“属性名/表达式” 对逗号列表组成。注意,Tutorial D中的关键字TUPLE具有双重使命——既用于我们见过的元组选择器调用,又用于更早见过的元组类型名称。类似说明也适用于关键字RELATION(参见3.3节“关系是什么”)。
定义的推论
此刻,我要强调一些前述定义的重要推论。
第一个推论:从来就没有元组包含null。这是因为,根据定义,每个元组都对它的每个属性包含一个(某种相应类型的)值,而null不是值(如第1章中所述),尽管SQL经常将null显式地用作null值。建议:因为“null值”(null value)一词作为术语自相矛盾,所以别用它;说“null”就行。注意,这个建议并不是故弄玄虚,它关乎正确的思考。SQL在处理null的方面存在很多错误,其中一些就是因为SQL有时(但也不总是)认为null是值。(事实上,这个矛盾恰恰体现在此概念在SQL标准中的定义:null值是一个特殊的值,用于表明数据值的缺失。换句话说,null是一个表示值不存在的值。)
如果没有任何元组能包含null,那么自然也没有关系可以包含null;因此,我们至少就有一个正式的理由来驳斥“null”的概念。第4章中,我还会给出一些更为实用化的理由。
第二个推论(或一对推论):元组的所有子集都是元组,标题的所有子集都是标题(第1章中提到过,现在要详细说明一下)。举个例子,对于常用的对应供应商S1的元组而言,我们所说的元组中“{SNO,CITY}取值”本身就是另一个(度为2的)元组:
它的标题如上图所示,其类型为TUPLE {SNO CHAR,CITY CHAR }。同样,下面也是一个元组:
该元组的度为1,类型为 {SNO CHAR }。
我想你肯定知道空集(empty set)——没有元素的集合——是任何集合的子集。所以,空标题是有效的标题!并且,分量集合为空集的元组也是有效的元组(在纸上画出这样元组的图示有些困难,我甚至都不会去尝试)。一个标题为空的元组的类型为TUPLE{};实际上,我们有时将其称为0-元组(0-tuple)以便强调其没有任何分量并且度为0。我们有时也把它叫作空元组(empty tuple)。你可能认为这样的元组在实践中没什么用;但是,它却是十分重要。这可能让你大吃一惊。3.7节“TABLE_DUM和TABLE_DEE”会对此进行详细说明。
现在先回到原先的供应商S1元组(度为4)。假设我们得到了这个元组并想从中得到一些属性(比如SNO属性)的实际值,那么我们就必须从包含它的元组中提取(extract)值。Tutorial D使用SNO FROM t语法形式达到此目的(其中,t可以是任何表达式,只要该表达式具有SNO属性的元组即可)。SQL使用点限定(dot qualification):t.SNO。
注意:从上文可知,一个值和一个包含值的元组不是一回事;尤其是当它们的类型不同时。此逻辑区分类似于第2章中“元组”和“包含元组的关系”之间的区别,它们也不是一回事(它们的类型也不相同)。
回到元组相等性(tuple equality)的概念(第1章中提过,现在要详细说明)。第2章中说过,“=”比较运算符是(实际上必须是)针对所有类型定义的,元组类型当然也不能例外。基本上,当且仅当两个元组是完全一样的元组时,这两个元组才是相等的(就好像两个整数是相等的,当且仅当它们是完全一样的整数时)。但是,仔细研读元组相等性的语义还是很有必要的,因为关系模型中依赖此概念的内容实在是太多了。比如,候选键、外键以及几乎所有的关系代数运算符都是依赖它进行定义的。下面是元组相等性的精确定义:
定义 元组t和t'是相等(equal)的,当且仅当它们具有相同的属性A1,A2,…, An(换言之,它们类型相同)并且,对于所有的i(i=1,2,…,n),t中Ai的值v与t'中Ai的值v'相等。
另外,两个元组是互为重复(duplicate)的,当且仅当它们是相等的(这第1章中说过,虽然显而易见但还是要说一下)。因此,图1.3中suppliers关系的供应商S1元组与它自己相等。所以,它对于它自己是重复的;而它与其他任何东西都不相等,不与其他任何东西(具体的说,就是其他元组)重复。
此外,上述定义的一个直接推论就是所有的0-元组都互为重复。因此,我们决定:如果要说术语就用0-元组(the 0-tuple)代替“一个”0-元组("a" 0-tuple)。实际上,我们一直都是这么做的。另外,我们可以说0-元组是任何元组的子集(就好像我们可以说空集是任何集合的子集一样)。
所以,比较运算符“=”和“≠”都很有必要适用于元组。然而,运算符“<”和“>”却不适用。原因在于,元组是基本集合(具体而言是分量集合),而“<”和“>”这样的运算符对于集合是无意义的。
在本节结束前,请读者注意一下本章结尾部分的练习3.16(在附录F中对此练习也有讨论),我强烈建议你在上面花些时间。本书后续章节会关注由此练习引申出的一些要点。