看似不正确的运算符优先级

时间:2018-08-02 20:43:23

标签: sql-server

我难以理解某些运算符优先级,因此我期望下面的 all 所有查询失败;这是基于下面的文档,在该文档中,将在比较运算符之前评估除法。

  

t-sql运算符优先级的Microsoft文档:https://docs.microsoft.com/en-us/sql/t-sql/language-elements/operator-precedence-transact-sql?view=sql-server-2017

样本数据和查询

CREATE TABLE #orders(
    price INT
)
GO 

INSERT INTO #orders VALUES 
(1)
,(2)
,(3)
,(4)
,(5)
,(6)
,(7)
,(8)
,(9)
,(10)
,(0)  

GO

/*
the following two queries work
*/
SELECT * 
FROM   #orders 
WHERE  2 / price > 0 
       AND price > 0; 

SELECT * 
FROM   #orders 
WHERE  price > 0 
       AND 2 / price > 0; 

/*
these don't work - getting divide by zero error
*/
SELECT * 
FROM   #orders 
WHERE  2 / price > 0 
       AND price IN ( 1, 2, 3, 4, 
                      5, 6, 7, 8, 
                      9, 10 ); 

SELECT * 
FROM   #orders 
WHERE  price IN ( 1, 2, 3, 4, 
                  5, 6, 7, 8, 
                  9, 10 ) 
       AND 2 / price > 0; 
  

其中一个工作查询的共享执行计划:https://www.brentozar.com/pastetheplan/?id=HJFH3JWB7

     

其中一个无效查询的共享(估计)执行计划:https://www.brentozar.com/pastetheplan/?id=r1Ds2yWSQ

在理解这种行为方面的任何帮助将不胜感激!

谢谢

Eli

3 个答案:

答案 0 :(得分:3)

好问题。我认为documentation page that you shared上的语言可能不太准确,可能会引起您的困惑。这是一个引号(强调我的意思):

  

当复杂表达式具有多个运算符时,运算符优先级   确定执行操作的顺序。的   执行顺序会严重影响结果值。

这里的强烈含义是优先级和执行顺序本质上是同一件事,但事实并非如此。 Eric Lippert explains this比以往任何时候都好(强调原文)。

  

优先级规则描述了带括号的表达式应如何使用   当表达式混合使用不同类型的   运算符。例如,乘法的优先级高于   另外,因此2 + 3 x 4等于2 +(3 x 4),而不是(2 + 3)x 4。

     

关联性规则描述了带括号的表达式   当表达式具有一堆相同的表达式时,应将其括起来   一种操作员。例如,加法从左到右是关联的   对,所以a + b + c等于(a + b)+ c,而不是a +(b + c)。在   普通的算术,这两个表达式总是给出相同的   结果;在计算机算术中,它们不一定。 (作为一个   运动可以找到a,b,c的值,使得(a + b)+ c为   不等于C#中的+(b + c)?)

     

现在这令人困惑。

     

求值顺序规则描述了每个操作数在其中的顺序   计算表达式。括号只是描述了   结果分组在一起; “先做括号”不是规则   C#。相反,C#中的规则是“严格评估每个子表达式   从左到右”。

阅读全部内容。埃里克(Eric)使用C#和C ++作为示例,但是他的注释比这更广泛地适用。正如他的建议,如果您将运算符优先级规则视为控制表达式组成部分的分组方式,而不是控制它们执行的顺序,那么您所看到的行为就更有意义了。这是您可以理解会失败但实际上成功的查询之一的简化​​版本:

declare @t table (x int);
insert @t values (0);

select * from @t where 2 / x > 0 and x > 0;

SQL Server的运算符优先级规则仅表示上述查询等效于:

-- Same query as above with operator precedence shown explicitly.
select * from @t where ((2 / x) > 0) and (x > 0);

在这种情况下,关于AND的有趣之处在于,如果您知道它的一个操作数为false,那么您就知道整个表达式为false,甚至不计算另一个操作数。一些编程语言指定了操作数求值的顺序。例如,the && operator in C#首先评估其左操作数,如果为假,则根本不评估其右操作数。但是AND operator in T-SQL没有一种或另一种主张;由优化器选择。

有很多文章专门探讨这种行为;例如here is a pretty detailed one。这有帮助吗?

答案 1 :(得分:1)

我认为这与运算符的优先级无关,但是优化程序选择哪种顺序来评估where子句。

在前两行中,必须where price > 0首先过滤行,以防止错误。

由于后两个使用IN,因此必须首先进行除法以限制行,然后评估IN过滤器。可以通过添加索引来防止这种情况...即使ID为主键,该键创建聚簇索引,并且由于优化器可以使用索引并在除法之前对行进行过滤,因此不会出现错误,或选择。

谓词应从price > 0 AND 2 / price > 0更改为2/price > 0

答案 2 :(得分:0)

实际上,我更喜欢更改表达式(不太可能拥有价格指数-或简单地使带有或不带有索引的价格都健壮起来):

#box