我会将测试重写为
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 的执行。我不确定为什么组合 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
我确实想到尝试使用德摩根定律将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
*/