《SQL与关系数据库理论——如何编写健壮的SQL代码》一2.2 相等性比较

简介: 本节书摘来华章计算机《SQL与关系数据库理论——如何编写健壮的SQL代码》一书中的第2章 ,第2.2节 C. J. Date 著 单世民 何英昊 许侃 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

本节书摘来华章计算机《SQL与关系数据库理论——如何编写健壮的SQL代码》一书中的第2章 ,第2.2节 C. J. Date 著 单世民 何英昊 许侃 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.2 相等性比较

尽管我刚刚说过要忽略用户定义类型,我在本节还是要假设关系变量S和SP中的供应商编号(SNO)属性是某种用户定义类型(或者是用户定义域),本书会出于简化目的将该类型称为SNO。类似的,假设关系变量P和SP中的零件编号(PNO)属性也是具有相同名称PNO的用户定义类型(域)。注意,这些假设对于我的论证而言并不是关键;但它们会让论证更令人信服,并或许能让论证更容易理解一些。
众所周知,只有两个值来自于同一个域时才可以在关系模型中比较它们是否相等。比如,下述比较(此比较可以是某个SQL查询WHERE子句的组成部分)很明显是合法的:

SP.SNO = S.SNO     /* 没问题 */
相反,下面这个明显不合法:
SP.PNO = S.SNO     /* 有问题 */

为什么不合法?因为零件编号和供应商编号是不同种类的事物,它们定义在不同的域上。所以,通常认为:DBMS注1应该回绝任何(显式或隐式)包含不同域取值相等性比较的关系运算尝试(连接,并等等)。比如,假设某一用户想要找到当前没有供应任何零件的供应商(如图1.3样例数据中的供应商S5)。下面使用SQL表述此查询:

SELECT S.SNO , S.SNAME , S.STATUS , S.CITY 
FROM   S
WHERE  NOT EXISTS
     ( SELECT *
       FROM     SP
       WHERE     SP.PNO = S.SNO )     /* 有问题 */

(此处没有终止的分号,这是因为这是一个表达式而不是一个语句。参见本章的练习2.24。)
如注释所述,该表述方式肯定不行。原因在于,(在最后一行)用户大概是想说WHERE SP.SNO=S.SNO,却错误地(可能只是敲错了)说成了WHERE SP.PN=S.SNO。假设我们实际谈论的是简单排版工(可能是),那么从DBMS来看,此时打断用户、高亮显示错误,并询问用户是否要在继续执行之前改正错误,这些应该都是友好的行为。
现在还不知道有哪个SQL产品会像我刚才建议的那样实际运行。对于当今的产品(依赖于你如何设置数据库),要么是查询会简单地失败,要么是由系统给出错误的回答。这不能算是错误的答案,或许是对于错误问题的正确答案。(这么说能让你舒服点么?)
因此,DBMS在相等性比较不合法时应该回绝类似SP.PNO=S.SNO的比较。然而,Codd认为用户有时会比DBMS知道的还多,所以他认为在此种情况下应该有一种方法让用户促使DBMS继续前行并完成比较(即使是相等性比较明显不合法)。要解释这个观点很困难,因为坦白地讲,我觉得它讲不通——不过可以试一试。假设你的任务是设计一个包含顾客和供应商的数据库,并且你决定要有一个顾客编号域和一个供应商编号域。你这样建立并装载了你的数据库,且没有问题地运行了一两年。在某一天,一个用户给出了一个你从未听说过的查询——“有没有我们的顾客也是我们的供应商?”这是一个完全合理的查询,而且这个查询会包含一个顾客编号与供应商编号之间的比较(跨域的比较)以便看它们是否相等。如果确实是这样,那么系统就一定不能阻止进行比较,系统也一定不能阻止构造这个合理的查询。
以此为基础,Codd提出了一些关系运算符的所谓“域检查重载(domain check override,DCO)”版本。比如,连接的DCO版本即使是在连接属性定义在不同域的情况下也会执行连接。从SQL角度来说,可以设想此提议通过一个包含在SQL查询中的新子句(IGNORE DOMAIN CHECKS)来实现,如下:

SELECT ... 
FROM   ...
WHERE  CUSTNO = SNO
IGNORE DOMAIN CHECKS

此新子句是可单独授权的,不会允许大多数用户使用它(或许只有DBA注2可以使用它)。
在进一步详细分析DCO之前,看一个更简单的示例。考虑下述两个对suppliers-and-parts数据库的查询:

SELECT ...                  │    SELECT ...
FROM   P , SP               │    FROM   P , SP
WHERE  P.WEIGHT = SP.QTY    │    WHERE  P.WEIGHT - SP.QTY = 0

假设重量和数量定义在不同的域上(很合理),那么左侧的查询显然不合法。右边的查询又会怎么样呢?根据Codd的观点,右边的是合法的!在他的《The Relational Model for Database Management》(Version 2 ,Addison-Wesley, 1990)第47页,Codd认为在此种情况下“DBMS[仅仅]检查基本数据类型是相同的”,就当前示例而言,这个“基本数据类型”(不严谨地讲)都只是数,所以检查成功。
对我而言,这个结论是不可接受的。显然,表达式P.WEIGHT=SP.QTY和P.WEIGHT-SP.QTY=0本质上是一回事。因此,它们必须都合法或者都不合法。认为一个合法而另一个不合法的观点肯定是讲不通的。所以,在我看来,Codd风格的域检查就有些怪异了,更别说什么域检查重载了。(实质上,Codd风格的域检查只在两个运算元都指定为简单属性引用这种非常特殊的情况下适用。可以看到,比较 P.WEIGHT = SP.QTY 属于此类情况,而比较P.WEIGHT-SP.QTY=0不属于此类情况)。
再来看看更简单的示例。考虑下述比较(每个都可以作为SQL WHERE子句的一部分出现):

S.SNO = 'X4'     P.PNO = 'X4'     S.SNO = P.PNO

我希望你认同:至少前两个貌似是合法的(可以成功计算,甚至可能为TRUE),而第三个是非法的。如果是这样,那么我希望你还能认同:此处是让人感觉奇怪的。这很明显,我们可以有3个值a、b和c,使得a=c为真,b=c为真,但是说到a=b,我们甚至不能进行比较,更别说会得到真了!所以会怎样?
事实是,属性S.SNO和P.PNO分别定义在域SNO和域PNO,以及对于域实际是类型的声明。如前面提到过,为了当前讨论,假设域SNO和域PNO都是用户定义类型。这些用户定义类型就有可能都由系统定义类型CHAR物理表示。事实上,为了明确我们假设确实是这样。然而,这些表示方式是“实现”的一部分而不是“模型”的一部分——它们和用户无关,并且(如第1章中看到的)应该对用户是隐藏的。尤其是,用于供应商编号的运算符和用于零件编号的运算符都是使用这些类型进行定义的,而不是使用CHAR类型进行定义的(参见2.4节“类型是什么”)。比如,我们可以连接两个字符串,但是我们或许不能连接两个供应商编号(只有在连接是一个用SNO类型定义的运算符时才能这么做)。
当我们定义一个类型时,我们必须定义可用于该类型取值及该类型变量的运算符(参见2.4节“类型是什么”)。我们必须定义的一个运算符是选择器(selector)运算符,此运算符允许我们选择(或指定)当前类型的任意值。注3以SNO类型为例,对应的选择器允许我们选择具有某个特定CHAR表现方式的特定SNO取值。示例如下:

SNO('S1')

该表达式是一个SNO选择器调用,返回一个确定的供应商编号;即,使用字符串'S1'表示的取值。类似的,表达式

PNO('P1')

是一个PNO选择器调用,返回确定的零件编号:即,由字符串'P1'表示的值。换句话说,SNO选择器和PNO选择器实际上是获取一个CHAR值,然后分别将其转换为SNO取值和PNO取值。
对于比较S.SNO='X4',如你所见,此处的比较元是不同类型的(具体说,是SNO类型和CHAR类型;事实上,'X4'是一个字符串字面值)。因为它们类型不同,所以它们肯定不相等(回顾前文所说的:只有当两个值来自于同一个域时,两个值才可以进行相等性比较)。但是系统至少知道有那么一个运算符(即SNO选择器),实际上会执行CHAR到SNO的转换。所以,系统可以(隐式)调用该运算符将CHAR比较元转换为供应商编号,从而实际上用下述比较替换原先的比较:

S.SNO = SNO('X4')

我们现在就是比较两个供应商编号了,这是合法的。
同理,实际上系统可以使用下述比较替换P.PNO='X4':

P.PNO = PNO('X4')

然而,就比较S.SNO=P.PNO而言,对系统来说并没有已知的转换运算符(至少,我们假设没有)可将供应商编号转换为零件编号或者进行反向转换,所以该比较因为类型错误(type error)而失败:比较元是不同类型的并且没有方法可以让它们成为同一类型。
注意:如上例描述的隐式类型转换在文献中常被称为型转(coercion)。因此,我们可以说字符串“X4'”在第一个示例中型转为SNO类型;在第二个示例中型转为PNO类型。对于SQL中的型转,会在2.7节“SQL中的类型检查和型转”中做更多的说明。
当我们定义类似SNO或PNO这样的类型时,另一个必须定义的运算符一般称为THE_运算符(THE_ operator),它实际上将确定的SNO或PNO取值转换为用于表示取值的字符串(或其他什么)。注4从示例角度出发,假设SNO和PNO类型的THE_运算符分别称为THE_SC和THE_PC。此时,如果确实要比较S.SNO和P.PNO是否相等,那么对于该请求唯一有意义的就是,检验相应的字符串表示是否相同,如下所示:

THE_SC ( S.SNO ) = THE_PC ( P.PNO )

换句话说:将供应商编号转换为字符串,将零件编号转换为字符串,然后比较两个字符串。
可以看出,这里描绘的(包含选择器和THE_运算符的)机制实际上同时提供了两种检查方法:(a)我们首先想要的域检查;(b)在需要时重载域检查。而且,该机制是以一种干净的、完全正交的非专案(non ad hoc)方式实现以上工作的。相反,域检查重载并没有真正完成此工作。事实上,它根本讲不通,因为它混淆了类型和表示(如前面提过的,类型是模型的概念,而表示是实现的概念)。注意:如果你不熟悉在语言设计原理中很重要的正交性(orthogonality),你可以在我的《Relational Database Relational Database Writings 1994—1997》(Addison -Wesley,1998)一书中“关于正交性的注解”部分读到关于它的内容。
现在,你可能意识到了,我此处所谈论的正是在语言圈子里所熟知的强类型化(strong typing)。不同的作者会对此术语有稍微不同的定义,但该术语基本上意味着:(a)任何事物(尤其是所有的值和所有的变量)都有类型;(b)只要我们尝试执行某些运算,系统就会检查运算元类型是不是对于当前运算正确的类型(或可以型转为正确的类型)。还可以看到的是,该机制不仅对于我所讨论的相等性比较适用,而且它对于所有运算也都适用。文献里关于域检查的讨论将重点放在相等性和其他比较运算之上,这是长期以来用法所限定的,但实际上是错位的。比如,考虑如下表达式:

P.WEIGHT * SP.QTY 

P.WEIGHT + SP.QTY

第一个表达式可能是合法的(它会产生另一个重量:即相关出货的总重量)。相反,第二个可能不合法(重量加上数量是要表示什么含义呢?)。
本节结束前,我要强调相等运算符(“=”)所扮演的绝对基础性的角色。上面的讨论并不是碰巧关注两个值的相等性比较问题。事实是,相等性真的是处于中心位置的,而且关系模型要求每个类型都支持它。事实上,类型就是值的集合(参见2.4节“类型是什么?”),所以如果没有“=”运算符,我们甚至都不能说出是什么取值构成了所讨论的类型!也就是说,对于确定的类型T和值v,在没有“=”运算符时,我们不能说v是否属于构成类型T的取值集合。
更进一步,关系模型也指定了“=”运算符的语义。如下:如果v1和v2是同一类型的取值,那么v1=v2在v1和v2是同一个值时为TRUE,否则为FALSE(事实上,你可能还记得,我在第1章中就这么说了)相反,如果v1和v2是不同类型的取值,那么v1=v2没有意义——它甚至不是合法的比较——除非v1可以型转为v2的类型,或v2可以型转为v1的类型,而此时我们真正谈论的就不再是v1和v2之间的此种比较了。

相关文章
|
13天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
52 10
|
13天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
80 6
|
2月前
|
SQL 数据库
小技巧:如何让 ABAP OPEN SQL 代码具有自解释性(Self-Explained)
小技巧:如何让 ABAP OPEN SQL 代码具有自解释性(Self-Explained)
25 0
|
1月前
|
SQL XML Java
整理几个常用的sql和其他代码
整理几个常用的sql和其他代码
12 1
|
1月前
|
SQL 存储 关系型数据库
【MySQL】——关系数据库标准语言SQL(大纲)
【MySQL】——关系数据库标准语言SQL(大纲)
56 0
【MySQL】——关系数据库标准语言SQL(大纲)
|
2月前
|
SQL Java 关系型数据库
Flink SQL 问题之用代码执行报错如何解决
Flink SQL报错通常指在使用Apache Flink的SQL接口执行数据处理任务时遇到的问题;本合集将收集常见的Flink SQL报错情况及其解决方法,帮助用户迅速恢复数据处理流程。
122 6
|
2月前
|
SQL 关系型数据库 MySQL
MySQL日期函数的SQL代码示例和使用场景
MySQL日期函数的SQL代码示例和使用场景
24 0
|
1月前
|
SQL 数据库 数据安全/隐私保护
Sql Server数据库Sa密码如何修改
Sql Server数据库Sa密码如何修改
|
23天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
17 0
|
1天前
|
SQL 关系型数据库 MySQL
:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi
:“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi
6 0