本节书摘来华章计算机《SQL与关系数据库理论——如何编写健壮的SQL代码》一书中的第3章 ,第3.2节 C. J. Date 著 单世民 何英昊 许侃 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.2 SQL中的行
SQL支持数据行而不是元组;具体来说,SQL支持行类型、行类型构造器和行值构造器。这些数据行基本可以分别类比于Tutorial D中的元组类型、TUPLE类型生成器和元组选择器(第2章中讨论过行类型以及行类型构造器,但没有行值构造器)。不过,这些类比是很不严谨的。这是因为,行与元组严格来讲并不一样,行对其分量有自左向右的排序,而元组没有。比如,表达式ROW(1,2)和ROW(2,1)就代表了两个不同的SQL行(两个都是SQL中合法的行值构造器调用)。注意:SQL行值构造器调用中的ROW关键字是可选的;但在实践中基本都忽略它。
正是因为自左向右的排序,SQL中的行分量(“字段”)可以(实际也是)通过顺序位置而不是名称来识别。比如,下面的行值构造器调用(实际是行字面值,尽管SQL没有这个术语):
( 'S1' , 'Smith' , 20 , 'London' )
此行很明显有一个值为'Smith'的分量。然而,我们在逻辑上不能说该分量是“SNAME分量”,我们只能说它是第2个分量。
还有,SQL中的行至少包含一个分量,SQL没有关系模型中0-元组的对应物(没有“0-行”)。
如第2章所述(涉及SQL行变量SRV的那个例子),SQL也支持行赋值操作。注2这样的赋值尤其存在于SQL的UPDATE语句中。比如,下面的UPDATE语句:
UPDATE S
SET STATUS = 20 , CITY = 'London'
WHERE CITY = 'Paris' ;
就定义为与下面的语句逻辑相等(注意在第2行中的行赋值):
UPDATE S
SET ( STATUS , CITY ) = ( 20 , 'London' )
WHERE CITY = 'Paris' ;
对于比较运算,大多数SQL中的布尔表达式(包括简单的“标量”比较)实际上都是依据行而不是标量进行定义的。下面是一个SELECT表达式示例,其中的WHERE子句包含一个显式的行比较:
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) = ( 20 , 'London' )
这个SELECT表达式逻辑等价于下面的语句:
SELECT SNO
FROM S
WHERE STATUS = 20 AND CITY = 'London'
再比如,表达式:
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) <> ( 20 , 'London' )
逻辑等价于:
SELECT SNO
FROM S
WHERE STATUS <> 20 OR CITY <> 'London'
注意:此例的展开形式,WHERE子句中的两个独立比较是由OR而不是由AND连接的。
另外,因为行分量具有左右排序,SQL也支持将“<”和“>”作为行比较运算符。示例如下:
SELECT SNO
FROM S
WHERE ( STATUS , CITY ) > ( 20 , 'London' )
此式逻辑等价于:
SELECT SNO
FROM S
WHERE STATUS > 20 OR ( STATUS = 20 AND CITY > 'London' )
然而,在实践中,大多数行比较包含的都是度为1的行,如下:
SELECT SNO
FROM S
WHERE ( STATUS ) = ( 20 )
前面示例中所有的比较表达式都是行值构造器调用。不过,SQL有一个语法规则:如果这样的调用由唯一一个用括号封闭的标量表达式构成,则可以去掉括号,如下:
SELECT SNO
FROM S
WHERE STATUS = 20
此例中WHERE子句的“行比较”就是有效的标量(scalar)比较(STATUS和20都是标量表达式)。然而,严格地讲,SQL中没有什么是标量比较;对于SQL而言,表达式STATUS=20在技术上仍然是一个行比较(“标量”比较元(comparand)有效地型转为行)。
建议:除非比较中各行的度都为1(即实际上的标量),否则就不要用“<”“<=”“>”和“>=”这些比较运算符;它们既依赖于自左向右的列排序,又没有关系模型中的对应物,还非常容易出错。(有必要说明一下,在SQL第一次提出这个功能时,标准制定者在准确定义语义时碰到了很大的困难;事实上,他们在搞定之前来回折腾了好几遍)。