SQL和逻辑运算符以及空检查

时间:2011-12-21 00:49:38

标签: sql sql-server postgresql null

我使用SQL Server多年来有一个模糊的,可能是货物崇拜的内存,当你有一个可能为空的列时,编写“WHERE”子句谓词就不安全了:

 ... WHERE the_column IS NULL OR the_column < 10 ...

它与SQL规则没有规定短路这一事实有关(事实上,这可能是出于查询优化的原因,这是一个坏主意),因此“&lt;”即使列值为空,也可以评估比较(或其他)。现在,确切地说为什么这是一件可怕的事情,我不知道,但我记得有些文件严厉警告总是将其编码为“CASE”条款:

 ... WHERE 1 = CASE WHEN the_column IS NULL THEN 1 WHEN the_column < 10 THEN 1 ELSE 0 END ...

(愚蠢的“1 =”部分是因为SQL Server没有/没有一流的布尔值,或者至少我认为它没有。)

所以我的问题是:

  1. 对于SQL Server(或者反向修改SQL Server 2000或2005)是否真的如此?或者我只是疯了吗?
  2. 如果是这样,同样的警告是否适用于PostgreSQL? (8.4如果重要的话)
  3. 究竟是什么问题?它与索引的工作原理有什么关系吗?
  4. 我在SQL方面的基础非常薄弱。

6 个答案:

答案 0 :(得分:11)

我不知道SQL Server所以我不能说出来。

给定某个逻辑运算符a L b的表达式L,无法保证在a之前或之后评估ba b }和WHERE将被评估:

  

Expression Evaluation Rules

     

未定义子表达式的评估顺序。特别是,操作员或功能的输入不一定是从左到右或以任何其他固定顺序进行评估。

     

此外,如果可以通过仅评估表达式的某些部分来确定表达式的结果,则可能根本不会评估其他子表达式。
  [...]
  请注意,这与某些编程语言中的布尔运算符从左到右的“短路”不同。

     

因此,使用具有副作用的函数作为复杂表达式的一部分是不明智的。依赖HAVINGthe_column IS NULL OR the_column < 10 条款中的副作用或评估顺序尤其危险,因为这些条款作为制定执行计划的一部分进行了广泛的重新处理。

表达形式:

NULL < n

担心,因为所有NULL的{​​{1}}为n,即使NULL < NULL评估为NULL,也无需担心;此外,NULL不是真的,所以

null is null or null < 10

只是一种复杂的说法true or nulltrue,无论首先评估哪个子表达式。

整个“使用CASE”听起来像是对我来说很危险的SQL。然而,像大多数货物崇拜一样,货物下面埋藏着一个真相;在我从PostgreSQL手册中摘录的第一部分之后,你会发现:

  

当强制执行评估顺序时,可以使用CASE结构(参见第9.16节)。例如,这是一种不值得信任的方法,试图在WHERE子句中避免被零除:

SELECT ... WHERE x > 0 AND y/x > 1.5;
     

但这很安全:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

因此,如果您需要防范会引发异常或有其他副作用的情况,那么您应该使用CASE来控制评估顺序,因为CASE是{{ 3}}:

  

每个 条件 是一个返回boolean结果的表达式。如果条件的结果为true,则CASE表达式的值是条件后面的 结果 ,以及CASE表达式的其余部分未处理。如果条件的结果不成立,则以相同的方式检查任何后续的WHEN子句。

所以给出了这个:

case when A then Ra
     when B then Rb
     when C then Rc
     ...

A保证在BB之前C之前进行评估,并且只要其中一个条件评估为真值,评估就会停止。

总之,CASE短路既不会AND也不会OR短路,因此您只需要在需要防护时使用CASE的效果。

答案 1 :(得分:1)

我从来没有听说过这样的问题,this bit of SQL Server 2000 documentation在一个例子中使用WHERE advance < $5000 OR advance IS NULL,所以它一定不是一个非常严厉的规则。我对OR的唯一担心是它的优先级低于AND,所以当你不是这个意思时,你可能会意外地写出像WHERE the_column IS NULL OR the_column < 10 AND the_other_column > 20这样的东西;但通常的解决方案是括号而不是大CASE表达式。

我认为在大多数RDBMS中,索引不包含空值,因此the_column上的索引对此查询不会非常有用;但即使不是这种情况,我也不明白为什么一个大的CASE表达式会更友好。

(当然,很难证明是消极的,也许其他人会知道你指的是什么?)

答案 2 :(得分:1)

好吧,我已经反复写过像第一个例子一样的查询,因为永远(哎呀,我已经编写了生成查询的查询生成器),而且我从来没有遇到过问题。

我想你可能记得有人在某个时候反对写出使用OR的时髦加入条件时给你的一些警告。在您的第一个示例中,OR加入的条件限制同一个表的同一列,这是正常的。如果您的第二个条件是连接条件(即,它限制来自两个不同表的列),那么您可能会陷入错误的情况,其中查询规划者别无选择,只能使用笛卡尔连接(坏,坏,坏!!! )。

我不认为您的CASE函数确实在那里做任何事情,除非可能妨碍您的查询计划程序尝试为查询找到一个好的执行计划。

但更一般地说,只需先编写简单的查询,然后查看它对真实数据的执行情况。无需担心可能甚至不存在的问题!

答案 3 :(得分:1)

而不是

the_column IS NULL OR the_column < 10

我做

isnull(the_column,0) < 10

或第一个例子

WHERE 1 = CASE WHEN isnull(the_column,0) < 10 THEN 1 ELSE 0 END ...

答案 4 :(得分:0)

空虚可能令人困惑。如果您尝试将Null OR值作为参数ex传递,则“... WHERE 1 = CASE ...”非常有用。 “WHERE the_column = @parameter。这篇文章可能会有帮助Passing Null using OLEDB

答案 5 :(得分:0)

CASE有用的另一个例子是在varchar列上使用日期函数。在使用之前添加ISDATE说转换(colA,datetime)可能不起作用,并且当colA具有非日期数据时,查询可能会出错。