SQL / T-SQL短路

时间:2014-06-08 21:08:02

标签: sql sql-server tsql

我在SQL Server 2012中有一个表,它有这些值(它们都是整数)。

     a      b
     1      1
     1      0
    -1      1
     2      1

此查询可以正常工作。

select * from T1 
where
b <> 0 and a / b > 0

此查询失败。

select * from T1 
where
b * b * b <> 0 and a / b > 0

我想我有一点想法,但仍然......为什么?你能清楚地解释(或指向一些官方文档)SQL Server短路有什么不可用?

我发现声明说T-SQL确实支持短路但是如果评估顺序不能保证那么......那么短路过程是不是模糊不清或者说没有明确定义?

我对此感到困惑。

3 个答案:

答案 0 :(得分:1)

SQL Server中SQL短路的规范非常模糊。从我所听到的,唯一可以确定您的查询将被延迟评估的是具有多个WHEN条目的CASE指令。即使您使用简单的OR / AND表达式也无法保证。如果您想了解更多信息,请参阅this article

答案 1 :(得分:1)

如果您尝试避免除以零错误,则可以使用NULLIF

 select * from T1 where b <> 0 and a / nullif(b,0) > 0

 select * from @t T1 where b * b * b <> 0 and a / nullif(b,0) > 0

将成功执行。

如果您想知道发生了什么,请查看实际的执行计划。

第一个查询将显示

的谓词
 [T1].[b]<>(0) 
 AND [T1].[a]/CASE WHEN [T1].[b]=(0) THEN NULL ELSE [T1].[b] END

其中第二个将评估为

[T1].[a]/[T1].[b]>(0) AND [T1].[b]*[T1].[b]*[T1].[b]<>(0)

我建议优化器认为乘以三个值比单个除法运算更复杂,所以首先计算 - 实际上,如果你将第二个查询改为

 select * from T1 where b * b * b <> 0 and a*a / b > 0

 select * from T1 where power(b,3)<> 0 and a / b > 0

它会再次成功执行。

您还会注意到,在第一个查询中更改过滤器的顺序没有区别 - 首先执行更简单的操作。

答案 2 :(得分:1)

我从书中学到了这个问题 - 内部Microsoft SQL Server:T-SQL查询。我复制书籍内容并在此发布。你的问题在之后得到解答 第一个例子。

关键概念 - 一次性操作

SQL支持一个名为all-at-once操作的概念,这意味着在同一个逻辑查询处理阶段出现的所有表达式都会被评估为在同一时间点。

这个概念解释了为什么,例如,您不能在同一SELECT子句中引用SELECT子句中指定的列别名,即使您应该能够直观地看待它。请考虑以下查询:

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

对列别名orderyear的引用在SELECT列表的第三个表达式中无效,即使引用表达式出现&#34;在&#34;之后分配别名的那个。原因是逻辑上没有评估SELECT列表中表达式的顺序 - 它是一组表达式。在逻辑级别,SELECT列表中的所有表达式都在同一时间点进行计算。因此,此查询会生成以下错误:

Msg 207, Level 16, State 1, Line 4
Invalid column name 'orderyear'.

这是一次性操作的相关性的另一个例子:假设你有一个名为T1的表,有两个名为col1和col2的整数列,你想要返回col2 / col1大于2的所有行。表中可能存在col1等于0的行,您需要确保在这些情况下不进行除法 - 否则,查询会因为被零除错误而失败。因此,如果您使用以下格式编写查询:

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

您假设SQL Server从左到右评估表达式,如果表达式为col1&lt;&gt; 0评估为FALSE,SQL Server将短路;也就是说,它不打算评估表达式10 / col1&gt; 2因为在这一点上已知整个表达式为FALSE。所以你可能认为这个查询永远不会产生被零除错误。

SQL Server确实支持短路,但由于ANSI SQL中的一次性操作概念,SQL Server可以按任何顺序自由处理WHERE子句中的表达式。 SQL Server通常基于成本估算做出这样的决策,这意味着通常首先评估评估成本较低的表达式。 您可以看到,如果SQL Server决定处理表达式10 / col1&gt; 2首先,由于被零除错误,此查询可能会失败。

您可以通过多种方式尝试避免失败。例如,保证评估CASE表达式的WHEN子句的顺序。因此,您可以按如下方式修改查询:

SELECT col1, col2
FROM dbo.T1
WHERE
  CASE
    WHEN col1 = 0 THEN 'no' – or 'yes' if row should be returned
    WHEN col2/col1 > 2 THEN 'yes'
    ELSE 'no'
  END = 'yes';

在col1等于零的行中,第一个WHEN子句的计算结果为TRUE,CASE表达式返回字符串'no'(如果要在col1等于零时返回行,则替换为'yes')。仅当第一个CASE表达式未计算为TRUE(意味着col1不为0)时,第二个WHEN子句才会检查表达式10 / col1是否为0。 2评估为TRUE。如果是,则CASE表达式返回字符串'yes。'在所有其他情况下,CASE表达式返回字符串'no。'仅当CASE表达式的结果等于字符串时,WHERE子句中的谓词才返回TRUE '是的。'这意味着这里永远不会有尝试除以零。

这种解决方法结果令人费解,在这种特殊情况下,我们可以使用更简单的数学解决方法来避免完全划分:

SELECT col1, col2
FROM dbo.T1
WHERE col1 <> 0 and col2 > 2*col1;

我将此示例包含在内,以解释独特且重要的一次性操作概念,以及SQL Server保证WHEN处理顺序的事实 CASE表达式中的子句。

此链接还有更多内容 - http://social.technet.microsoft.com/wiki/contents/articles/20724.all-at-once-operations-in-t-sql.aspx