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