Я бы переписал тест как
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 или ноль. Этот план использует столбец зонда в своих внешних соединениях и устанавливает его в значение false, если тест EXISTS
не пройден (а не просто отбрасывается строка). Это означает, что в следующее соединение всегда подается 1 строка, и оно всегда выполняется.
Версия CASE
имеет очень похожий план, но использует предикат PASSTHRU
, который используется для пропуска выполнения JOIN, если предыдущее условие THEN
не было выполнено. Я не уверен, почему объединенные AND
не будут использовать тот же подход.
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
*/