13

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

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

END

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

谢谢克里什

4

7 回答 7

11

我会将测试重写为

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

这保证了此处描述的短路,但确实意味着您需要选择最便宜的一个进行预先评估,而不是将其留给优化器。

在我下面的极其有限的测试中,以下似乎在测试时适用

1.EXISTS AND EXISTS

EXISTS AND EXISTS版本似乎最有问题。这将一些外部半连接链接在一起。在任何情况下,它都没有重新安排测试的顺序,以尝试先进行更便宜的测试(本文后半部分讨论了这个问题)。在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 ...是,对于前者,如果条件为假,那么正确的行为是不返回任何结果,因此它只是链接OUTER SEMI JOINS,如果一个为假,则零行结转到下一个。

然而,IF版本总是需要返回 1 或 0 的结果。该计划在其外部连接中使用探测列,如果EXISTS测试未通过,则将其设置为 false(而不是简单地丢弃该行)。这意味着总是有 1 行馈入下一个 Join 并且它总是被执行。

CASE版本有一个非常相似的计划,但它使用一个谓词,如果不满足PASSTHRU先前的条件,它会使用该谓词跳过执行 JOIN 。THEN我不确定为什么 combine ANDs 不会使用相同的方法。

2.EXISTS OR EXISTS

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

/*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
*/

3. 添加一个ELSE

我确实想到尝试将德摩根定律转换ANDOR并看看这是否有任何不同。转换第一个查询给出

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条件的顺序,它现在短路!

IF (NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)  
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1))
PRINT 'N'
ELSE
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
于 2011-04-04T20:32:06.287 回答
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
于 2011-04-04T19:04:53.320 回答
2

如果我用 AND 执行查询,即使那样,两个表都被访问

SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values where [name] = 'rpcc') and 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。

于 2011-04-05T03:39:41.313 回答
2

我从 sqlteam 上的以下博客条目中引用以下引号:

SQL Server 如何短路 WHERE 条件评估

它会在你喜欢的时候出现,但不是你立即想到的方式。

作为开发人员,您必须知道SQL Server 不会像在其他编程语言中那样进行短路,并且您无法强制它执行.

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

SQL Server 是否短路?

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

于 2011-04-12T16:29:43.423 回答
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 上进行搜索。这对我来说似乎很奇怪。对此有什么想法吗?

于 2011-04-05T04:54:06.753 回答
0

没有。

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

这很容易测试。

对于您的第一次评估,请执行类似IF 1=0的操作,对于您的第二次评估,请执行任何操作,然后显示实际的执行计划。在我的情况下,它只进行常量扫描来评估这些常量。

于 2011-04-04T19:03:14.827 回答
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
于 2011-04-14T08:49:00.643 回答