Я бы переписал тест как
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
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
*/
person
Martin Smith
schedule
04.04.2011