2011-12-21 25 views
9

我从SQL Server的多年工作中得到了一个模糊的,可能是货运信息的内存,当你有一个可能为null的列时,编写“WHERE”子句是不安全的谓词,如:SQL和逻辑运算符以及空值检查

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

它有事可做的事实,SQL规则没有规定短路(事实上这是一个坏主意种,一种可能的查询优化的原因),因此“ <“比较(或其他)即使列值为空也可以被评估。现在,正是为什么这会是一件可怕的事情,我不知道,但我记得一些文件被正告总是代码为“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. 究竟是什么问题?它是否与索引如何工作有关?

我在SQL中的基础很弱。

+1

也许他们都在谈论和?由于null和任何内容都为空,因此在表达式可能包含空项的情况下,经常需要合并或案例。 – 2011-12-21 08:19:18

回答

10

我不知道SQL服务器,所以我不能到说话。

鉴于一些逻辑运算符L表达a L b,也不能保证a会前或b后,甚至双方ab将被评估进行评估:

Expression Evaluation Rules

的没有定义子表达式的评估顺序。特别是,操作员或功能的输入不一定是从左到右或以任何其他固定顺序进行评估。此外,如果一个表达式的结果只能通过评估它的某些部分来确定,那么其他的子表达式可能根本就不会被评估。

请注意,这与在某些编程语言中发现的布尔运算符从左到右的“短路”不同。

因此,使用具有副作用的函数作为复杂表达式的一部分是不明智的。依靠WHEREHAVING条款中的副作用或评估顺序是特别危险的,因为这些条款作为制定执行计划的一部分而被广泛地重新处理。

至于形式的表达式:

the_column IS NULL OR the_column < 10 

而言,没有什么可担心的,因为NULL < nNULL所有n,甚至NULL < NULL计算结果为NULL;此外,NULL是不是真的那么

null is null or null < 10 

是说true or null的只是一种复杂的方式,这就是true无论哪个子表达式先求的。

整个“使用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来控制评估顺序为CASEevaluated in order

每个条件是一个返回boolean结果的表达式。如果条件结果为真,CASE表达式的值是该条件之后的结果,并且不处理CASE表达式的其余部分。如果条件的结果不成立,则以相同的方式检查后续的WHEN子句。

所以给出这样的:

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

A是保证评估B之前,BC等,并评估之前尽快的条件之一计算为真值停止。

总之,CASE短路的击打既不AND也不OR短路,所以你只需要使用一个CASE当你需要防止副作用。

+1

是的,谢谢;我了解SQL不强制实施短路(或者,“非短路”)规则。问题的关键在于,如果普通的关系比较是针对可能为null的列进行评估,那么是否会发生可怕的事情。感谢您提供非常详细的答案。 – Pointy 2011-12-21 04:47:12

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表达式对索引更友好。

(当然,这是很难证明负面的,也许别人会知道你指的是什么?)

1

嗯,我已经多次因为是永远的写的第一个例子查询(哎呀,我已经写了产生这样的查询的查询生成器),我从来没有遇到过的问题。

我想你可能记得有人给你一些警告,反对写作时髦的加入条件使用OR。在你的第一个例子中,由OR加入的条件限制了同一个表的同一列,这是可以的。如果你的第二个条件是连接条件(即,它限制列来自两个不同的表),那么你可以进入坏的情况下查询规划只是没有选择,只能使用一个笛卡儿连接(坏,坏,坏的! )。

我不认为你的情况的功能实在是做任何事情在那里,除了在寻找的查询良好的执行计划可能妨碍你的查询规划的企图。

但更普遍,只是先写简单的查询,看看它是如何执行的真实数据。无需担心可能不存在的问题!

0

空值可能会造成混淆。如果您试图传递Null或Value作为参数ex,则“... WHERE 1 = CASE ...”非常有用。 “WHERE the_column = @parameter。这篇文章可能会有所帮助Passing Null using OLEDB

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 ... 
0

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