同时操作(All-At-Once Operation)

  SQL支持一种所谓的同时操作的概念,其含义是认为在同一逻辑查询处理阶段中出现的所有表达式都是同时进行计算的。

  例如,这个概念就可以解释为什么不能在SELECT子句中引用为同一SELECT子句中的列分配的别名,即使直觉上看起来应该能够这么做。考虑以下查询:

1 SELECT orderid,YEAR(orderdate) AS orderyear,orderyear + 1 AS nextyear
2  FROM Sales.Orders;

  以上SELECT列表中第三个表达式对orderyear这一列名的引用是无效的,即使引用表达式位于这个别名的定义“之后”。因为从逻辑上来说,SELECT列表中各表达式的计算是没有顺序的——它们只是一组表达式。在逻辑上SELECT列表中的所有表达式都是在同一时刻进行计算的。因此该查询会生成以下输出:

消息 207,级别 16,状态 1,第 1
列名
'orderyear' 无效。

  再举一个与同时操作有关的例子:假设你有一个称为T1的表,它有两个整数列:col1和col2,现在想返回col2/col1大于2的所有行。因为表中一些行的col1列可能等于0,所以需要确保不能对这些列执行除法运算,否则,查询会因为除数为0的错误而失败。为此,你写下了以下格式的一条查询语句:

1 SELECT col1,col2
2  FROM dbo.T1
3  WHERE col1 <> 0 AND col2/col1 > 2;

  这条语句假设SQL Server按从左到右的顺序来计算各表达式,如果表达式col1 <> 0的结果为FALSE,SQL Server会按照“短路(short-circuit)求值的原则,停止计算这个表达式;也就是说,它不会再多此一举地计算表达式col2/col1>2,因为这时已经知道整个表达式的结果为FALSE。所以你可能认为这个查询应该绝不会发生除数为0的错误。SQL Server确实支持“短路求值”,但因为ANSI SQL中有“同时操作”这么个概念,所以SQL Server可以按它喜欢的任意顺序来自由地处理WHERE子句中的表达式。对于这类问题,SQL Server通常是基于代价估计的标准来做出决定的,也就是说,通常是先计算需要付出较小代价的表达式。可以看到,如果SQL Server决定先处理表达式col2/col2>2,那么该查询可能会因为除数为0的错误而失败。

  为了尽可能避免查询执行失败,此处可以采用几种方法。例如,CASE表达式中的WHEN子句的计算顺序是有保障的。所以,可以将上面的查询修改如下:

1 SELECT col1,col2
2  FROM dbo.T1
3 WHERE
4 CASE
5 WHEN col1 = 0 THEN 'no'
6 WHEN col2/col1 > 2 THEN 'yes' --or 'yes' if row should be returned
7 ELSE 'no'
8 END = 'yes';

  如果某个行的col1列等于0,第一个WHEN子句的计算结果就为TRUE,CASE表达式就会返回字符串'no'(如果当col1等于0时返回该行,可以将'no'换为'yes')。只有当第一个CASE表达式的计算结果不等于TRUE(即col1不为0),第二个WHEN子句才会检查表达式col2/col1>2的记过是否为TRUE。如果为TRUE,CASE表达式则返回字符串'yes'。对于其他所有情况,CASE表达式将返回字符串'no'。只有当CASE表达式的计算结果等于字符串'yes'时,WHERE子句中的谓词才会返回TRUE。这样,在表达式中就肯定不会出现除数为0的错误了。

  这种解决办法实际上显得很啰嗦,在这个特定的例子中,我们可以使用一种更简单的数学办法来避免除数为0的错误:

1 SELECT col1,col2
2 FROM dbo.T1
3 WHERE col1 <> 0 AND col2 > 2*col1;

  本节通过一个例子解释了“同时操作”这一SQL独特而又重要的概念,以及SQL Server可以确保CASE表达式中WHEN子句的处理顺序这一事实。

posted @ 2010-12-18 20:11  宁静月光  Views(279)  Comments(0Edit  收藏  举报