SQL Server 条件流
Posted
技术标签:
【中文标题】SQL Server 条件流【英文标题】:SQL Server Conditional Flow 【发布时间】:2011-07-29 09:55:36 【问题描述】:如果我在IF EXISTS
条件中编写两个SELECT
语句,并在这些选择查询之间使用AND
子句,那么即使第一个SELECT
返回false,这两个查询是否都会执行?
IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN
END
在这种情况下,SQL Server 引擎是否同时执行 SQL 语句?
谢谢 克里什
【问题讨论】:
抱歉,我输入的不是“AND”,而是“OR” 【参考方案1】:您可以通过以下方式阻止第二次扫描:
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
【讨论】:
【参考方案2】:我从 sqlteam 上的以下博客条目中引用以下引语:
How SQL Server short-circuits WHERE condition evaluation
当你喜欢它的时候它会做,但不是你立刻想到的那样。
作为开发人员,您必须意识到 SQL Server 不会像在其他编程语言中那样进行短路,并且您无法强制它这样做.
欲了解更多详情,请查看上述博客条目中的第一个链接,该链接指向另一个博客:
Does SQL Server Short-Circuit?
最终判决?好吧,我还没有真正的,但可以肯定地说,唯一可以确保特定短路的情况是在 CASE 表达式中表达多个 WHEN 条件时。标准布尔表达式,优化器将根据您正在查询的表、索引和数据移动它认为合适的东西。
【讨论】:
【参考方案3】:我会将测试重写为
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 ...
版本之间行为差异的原因是,对于前者,如果条件为假,那么正确的行为是不返回任何结果,因此它只是链接 OUTER SEMI JOINS
,如果一个为假,则零行结转到下一行。
但是IF
版本总是 需要返回 1 或零的结果。该计划在其外部连接中使用一个探测列,如果 EXISTS
测试未通过(而不是简单地丢弃该行),则将其设置为 false。这意味着总是有 1 行馈入下一个 Join 并且它总是被执行。
CASE
版本有一个非常相似的计划,但它使用了一个 PASSTHRU
谓词,如果不满足之前的 THEN
条件,它会使用该谓词跳过 JOIN 的执行。我不确定为什么组合 AND
s 不会使用相同的方法。
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
我确实想到尝试使用德摩根定律将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
条件的顺序,它现在确实短路了!
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
*/
【讨论】:
CASE 不能保证短路...bartduncansql.wordpress.com/2011/03/03/… 打破了一些幻想,嗯? @gbn - 它保证在运行时短路!这是一个持续的折叠问题,SQL Server 甚至不会生成计划。 AFAIK,如果您有执行计划,则没有短路的例子。 啊,也承认了错误。错过了更新。也发现了这个***.com/questions/789231/… @gbn - But see this【参考方案4】:有一个有趣的观察。我有两张桌子 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 上进行搜索。这对我来说似乎很奇怪。对此有什么想法吗?
【讨论】:
我认为 FK 在这里没有任何区别。不同之处似乎在于,在WHERE
子句中它确实 短路了这个查询。我得到以下版本 1 的不同统计信息:if exists (select 1 from tbla where idvalue = -1) and exists (select 1 from tblb where idvalue= 1) print 'Y'
和版本 2:select 1 where exists (select 1 from tbla where idvalue = -1) and exists (select 1 from tblb where idvalue= 1)
【参考方案5】:
如果我使用 AND 执行查询,即使这样,两个表都会被访问
设置统计 IO 开启 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。
【讨论】:
我遇到了同样的问题。 - 克里什 +1 那么你已经证明你绝对不能依赖它短路!将执行计划与执行计划进行比较很有趣。IF CASE WHEN EXISTS (SELECT * from master..spt_values where [name] = 'rpcc') THEN CASE WHEN EXISTS (SELECT * from master..spt_monitor where pack_sent = 5235252) THEN 1 END END = 1 PRINT 'Y'
它们几乎相同,但 CASE
使用 PASSTHRU
谓词并且不涉及第二个表。【参考方案6】:
我相信您可以依赖大多数(如果不是全部)现代语言中的 IF 语句的短路行为。您可以尝试通过首先放置一个真实条件并用 1/0
替换您的第二个条件来进行测试,如果没有发生短路,这会给您一个除以零的错误,如下所示:
IF 1>0 OR 1/0 BEGIN
PRINT 'Short Circuited'
END
如果您不相信这一点,您可以随时重写查询来执行此操作:
IF EXISTS(SELECT...) BEGIN
IF EXISTS(SELECT...) BEGIN
...
END
END
【讨论】:
您不能/在 SQL Server 中通常依赖于此。根据经验,优化器可以重新安排大多数事情。我会使用第二个选项来确保我想要的行为发生了。在这里讨论***.com/questions/5195094/sql-server-predicates-lazy【参考方案7】:没有。
我刚刚在 SQL Server 2008 中进行了测试,如果第一次评估失败,它会立即跳过 IF
块。
这很容易测试。
对于您的第一次评估,请执行IF 1=0
之类的操作,对于您的第二次评估,请执行任何操作,然后显示实际的执行计划。在我的情况下,它只进行常量扫描来评估这些常量。
【讨论】:
如果我使用变量,它的工作原理与你在 SQL Server 中所说的完全一样,但如果你使用 EXISTS (SELECT...),我认为它不工作。我发现 SQL Server 执行这两个查询。我不知道如何在不将我的语句分成多个 IF 的情况下阻止 SQL Server 停止执行两者。 @Krish - 你如何验证这一点?如果我这样做SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values) OR EXISTS(SELECT * from master..spt_monitor) PRINT 'Y'
,那么我看到只有 1 个表被访问,尽管这两个表都出现在计划中。
嗨,马丁,感谢您的回复。一旦我将 where 条件添加到您的查询中,查询计划就会不同。 SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values where [name] = 'rpcc') or EXISTS(SELECT * from master..spt_monitor where pack_sent = 5235252) PRINT 'Y'
@Krish - 但是这两个条件都是错误的。所以它需要检查它执行的任何一个顺序,看看另一个是否为真。
Martin, SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values where [name] = 'rpcc') and EXISTS(SELECT * from master..spt_monitor where pack_sent = 523525233333) PRINT' Y',如果我运行此查询,即使两个查询都返回 false,也会同时运行。以上是关于SQL Server 条件流的主要内容,如果未能解决你的问题,请参考以下文章
sql server创建视图添加where条件,条件包含一个参数