SQL Server条件流

时间:2011-04-04 18:54:09

标签: sql sql-server tsql if-statement short-circuiting

如果我在SELECT条件中编写了两个IF EXISTS语句,并且在这些选择查询之间有AND子句,即使第一个SELECT返回,两个查询都会被执行假?

IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN

END

SQL Server Engine是否在这种情况下同时执行SQL语句?

由于 Krish

7 个答案:

答案 0 :(得分:11)

我会将测试重写为

IF CASE
     WHEN EXISTS (SELECT ...) THEN CASE
                                   WHEN EXISTS (SELECT ...) THEN 1
                                 END
   END = 1  

这可以保证短路as described here,但这意味着您需要选择最便宜的一个进行评估,而不是将其留给优化器。

在我极为有限的测试中,以下似乎在测试时仍然适用

1。 EXISTS AND EXISTS

EXISTS AND EXISTS版本似乎最成问题。这chains together some outer semi joins。在所有情况下都没有重新安排测试的顺序,试图先做更便宜的测试(an issue discussed in the second half of this blog post)。在IF ...版本中,如果没有短路则不会有任何差别。但是,如果将此组合谓词放入WHERE条款中,则计划会发生变化,并且短路,以便重新排列可能会有所帮助。

/*All tests are testing "If False And False"*/

IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)  
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
PRINT 'Y'
/*
Table 'spt_values'. Scan count 1, logical reads 9
Table 'spt_monitor'. Scan count 1, logical reads 1
*/

IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1) 
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2) 
PRINT 'Y'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/

SELECT 1
WHERE  EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)  
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/

SELECT 1
WHERE  EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1) 
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2) 
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_values'. Scan count 1, logical reads 9

*/

所有这些计划看起来非常相似。 SELECT 1 WHERE ...版本和IF ...版本之间行为差异的原因是,对于前者,如果条件为假,则正确的行为是不返回结果,因此它只链接{{ 1}}如果一个为假,则零行继续前进到下一个。

但是,OUTER SEMI JOINS版本始终需要返回1或0的结果。此计划在其外连接中使用探测列,如果未传递IF测试(而不是简单地丢弃该行),则将其设置为false。这意味着总有一行进入下一个Join,它总是被执行。

EXISTS版本的计划非常相似,但它使用CASE谓词,如果未满足之前的PASSTHRU条件,它会跳过JOIN的执行。我不确定为什么合并THEN s不会使用相同的方法。

2。 AND

EXISTS OR EXISTS版本使用连接(EXISTS OR EXISTS)运算符作为外部半连接的内部输入。这种安排意味着它可以在第一个返回时立即停止从内侧请求行(即它可以有效地短路)所有4个查询最终得到相同的计划,其中首先评估更便宜的谓词。

UNION ALL

3。添加/*All tests are testing "If True Or True"*/ IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1) OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1) PRINT 'Y' /* Table 'Worktable'. Scan count 0, logical reads 0 Table 'spt_monitor'. Scan count 1, logical reads 1 */ IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1) OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1) PRINT 'Y' /* Table 'Worktable'. Scan count 0, logical reads 0 Table 'spt_monitor'. Scan count 1, logical reads 1 */ SELECT 1 WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1) OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1) /* Table 'Worktable'. Scan count 0, logical reads 0 Table 'spt_monitor'. Scan count 1, logical reads 1 */ SELECT 1 WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1) OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1) /* Table 'Worktable'. Scan count 0, logical reads 0 Table 'spt_monitor'. Scan count 1, logical reads 1 */

我确实尝试过De Morgan的法律将ELSE转换为AND,看看是否有任何区别。转换第一个查询给出了

OR

因此,这对短路行为仍然没有任何影响。但是,如果您移除IF NOT ((NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2) OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1))) PRINT 'Y' ELSE PRINT 'N' /* Table 'spt_monitor'. Scan count 1, logical reads 1 Table 'spt_values'. Scan count 1, logical reads 9 */ 并反转NOT条件的顺序,则现在短路!

IF ... ELSE

答案 1 :(得分:7)

我相信你可以依赖大多数(如果不是全部)现代语言中的IF语句的短路行为。您可以先测试一个真正的条件,然后用1/0替换第二个条件进行测试,如果没有发生短路,这会给你一个零除错误,如下所示:

IF 1>0 OR 1/0 BEGIN
  PRINT 'Short Circuited'
END

如果您不相信,可以随时重写查询以执行此操作:

IF EXISTS(SELECT...) BEGIN
  IF EXISTS(SELECT...) BEGIN
    ...
  END
END

答案 2 :(得分:2)

如果我使用AND执行查询,即使这样,也会访问这两个表

SET STATISTICS IO ON IF EXISTS(SELECT * from master..spt_values where [name] ='rpcc')和EXISTS(SELECT * from master..spt_monitor where pack_sent = 5235252)PRINT'Y'

表'spt_monitor'。扫描计数1,逻辑读取1,物理读取0,预读取读取0,lob逻辑读取0,lob物理读取0,lob预读读取0。 表'spt_values'。扫描计数1,逻辑读取17,物理读取0,预读取读取0,lob逻辑读取0,lob物理读取0,lob预读读取0。

答案 3 :(得分:2)

我在sqlteam的以下博客条目中引用了以下引用:

<强> How SQL Server short-circuits WHERE condition evaluation

  

感觉就好了,但不会像你想象的那样。

     

作为一名开发人员,您必须意识到 SQL Server不会发生短路,就像在其他编程语言中完成一样没有什么可以强迫它

有关详细信息,请查看上述博客条目中的第一个链接,该链接指向另一个博客:

Does SQL Server Short-Circuit?

  

最终裁决?好吧,我还没有真的有一个,但可以肯定地说,唯一可以确保特定短路的时候是在CASE表达式中表达多个WHEN条件。 With标准布尔表达式,优化器将根据您查询的表,索引和数据在其认为合适时移动。

答案 4 :(得分:1)

有一个有趣的观察。我有两个表tbla和tblb。 tbla有一个主键(idvalue),用作tblb中的外键。两者都有一行idvalue = 1,但没有idvalue为-1的行。现在,下面的查询只使用一个表

select 1
where exists
(select 1 from tbla where idvalue = -1)
and exists (select 1 from tblb where idvalue= 1)

给出

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tbla'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

这是显而易见的,因为优化器知道由于存在主键 - 外键关系,因此如果tbla中缺少该值,则它永远不会出现在tblb中。因此,优化器将决定运行时不需要在tblb上进行搜索。

但是,如果我将查询写为

select 1
where exists
(select 1 from tbla where idvalue = 1)
and exists (select 1 from tblb where idvalue= -1)

然后访问这两个表。这很明显,因为优化器知道它必须检查两个地方以确保满足AND条件。

但是,在这两种情况下,实际执行计划都显示了对tbla和tblb的搜索。这对我来说很奇怪。有什么想法吗?

答案 5 :(得分:0)

<强>不。

我刚刚在SQL Server 2008中测试过,如果第一次评估失败,它会立即跳过IF块。

这很容易测试。

对于您的第一次评估,请执行类似IF 1=0的操作,并为您的第二次做任何事情,然后显示实际的执行计划。在我的作品中,它只进行恒定扫描以评估这些常数。

答案 6 :(得分:0)

您可以通过执行以下操作来阻止第二次扫描:

declare @test bit
select @test = case when exists(select 1...) then 1 else 0 end
if @test = 1
begin
    --1st test passed
    select @test = case when exists(select 2...) then 1 else 0 end
end
if @test = 1
begin
    print 'both exists passed'
end