如果我在SELECT
条件中编写了两个IF EXISTS
语句,并且在这些选择查询之间有AND
子句,即使第一个SELECT
返回,两个查询都会被执行假?
IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN
END
SQL Server Engine是否在这种情况下同时执行SQL语句?
由于 Krish
答案 0 :(得分:11)
我会将测试重写为
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
这可以保证短路as described here,但这意味着您需要选择最便宜的一个进行评估,而不是将其留给优化器。
在我极为有限的测试中,以下似乎在测试时仍然适用
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不会使用相同的方法。
AND
EXISTS OR EXISTS
版本使用连接(EXISTS OR EXISTS
)运算符作为外部半连接的内部输入。这种安排意味着它可以在第一个返回时立即停止从内侧请求行(即它可以有效地短路)所有4个查询最终得到相同的计划,其中首先评估更便宜的谓词。
UNION ALL
/*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