Условный поток SQL Server

Если я напишу два оператора SELECT в условии IF EXISTS с предложением AND между этими запросами выбора, будут ли выполнены оба запроса, даже если первый SELECT вернет false?

IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN

END

Выполняет ли SQL Server Engine оба оператора SQL в этом сценарии?

Спасибо Криш


person Krish    schedule 04.04.2011    source источник
comment
извините, вместо И я набрал ИЛИ   -  person Krish    schedule 04.04.2011


Ответы (7)


Я бы переписал тест как

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 условие не было выполнено. Я не уверен, почему комбинированные 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
*/
person Martin Smith    schedule 04.04.2011
comment
CASE не гарантирует короткое замыкание ... bartduncansql.wordpress.com/2011/03/03/, что разбивает несколько иллюзий, а? - person gbn; 13.04.2011
comment
@gbn - Тем не менее, короткое замыкание во время работы гарантировано! Это постоянная проблема сворачивания, и SQL Server даже не сгенерирует план. AFAIK, если у вас есть план выполнения, нет примера его короткого замыкания. - person Martin Smith; 13.04.2011
comment
ах, тоже признанная ошибка. Пропустил обновление. И нашел это тоже stackoverflow.com/questions/789231/ - person gbn; 13.04.2011
comment
@gbn - Но посмотрите на это - person Martin Smith; 19.09.2011

Я считаю, что вы можете положиться на короткое замыкание операторов IF в большинстве, если не во всех, современных языках. Вы можете попробовать проверить, поставив сначала истинное условие и заменив второе условие на 1/0, что даст вам деление на ноль ошибки, если короткого замыкания не происходит, например:

IF 1>0 OR 1/0 BEGIN
  PRINT 'Short Circuited'
END

Если вы этому не доверяете, вы всегда можете переписать свой запрос так:

IF EXISTS(SELECT...) BEGIN
  IF EXISTS(SELECT...) BEGIN
    ...
  END
END
person Tony Casale    schedule 04.04.2011
comment
Вы не можете / обычно полагаться на это в SQL Server. Как правило, оптимизатор может изменить большинство вещей. Я бы использовал второй вариант, чтобы убедиться, что произошло желаемое мной поведение. Обсуждаемые здесь stackoverflow.com/questions/5195094/sql-server-predicates- ленивый - person Martin Smith; 04.04.2011

Если я выполню запрос с И, даже тогда, обе таблицы будут доступны

УСТАНОВИТЬ СТАТИСТИКУ ВВОДА-ВЫВОДА НА ЕСЛИ СУЩЕСТВУЕТ (ВЫБРАТЬ * из master..spt_values, где [name] = 'rpcc') и СУЩЕСТВУЕТ (ВЫБРАТЬ * из master..spt_monitor, где pack_sent = 5235252) ПЕЧАТЬ '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.

person Medha    schedule 05.04.2011
comment
Я столкнулся с той же проблемой. - Криш - person Krish; 05.04.2011
comment
+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 и не касается второй таблицы. - person Martin Smith; 06.04.2011

Я беру следующие цитаты из следующей записи блога о sqlteam:

Как SQL Server прерывает оценку условия WHERE

Это происходит тогда, когда хочется, но не так, как вы сразу думаете.

Как разработчик вы должны знать, что SQL Server не выполняет коротких замыканий, как это делается в других языках программирования, и вы ничего не можете сделать, чтобы заставить его работать.

Для получения дополнительных сведений проверьте первую ссылку в приведенной выше записи блога, которая ведет к другому блогу:

Короткое замыкание SQL Server? < / а>

Окончательный вердикт? Что ж, на самом деле у меня его еще нет, но, вероятно, можно с уверенностью сказать, что единственное время, когда вы можете гарантировать конкретное короткое замыкание, - это когда вы выражаете несколько условий WHEN в выражении CASE. с стандартные логические выражения, оптимизатор будет перемещать вещи по своему усмотрению на основе таблиц, индексов и данных, которые вы запрашиваете.

person MicSim    schedule 12.04.2011

Было интересное наблюдение. У меня две таблицы 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. Мне это кажется странным. Есть мысли по этому поводу?

person Medha    schedule 05.04.2011
comment
Я не думаю, что FK имеет здесь какое-либо значение. Разница, похоже, в том, что в предложении WHERE он выполняет короткое замыкание этого запроса. Я получаю разные статистические данные для следующих версий Version1: if exists (select 1 from tbla where idvalue = -1) and exists (select 1 from tblb where idvalue= 1) print 'Y' и Version2: select 1 where exists (select 1 from tbla where idvalue = -1) and exists (select 1 from tblb where idvalue= 1) - person Martin Smith; 06.04.2011

Нет.

Я только что тестировал SQL Server 2008, и если первая оценка не удалась, блок IF сразу же пропускается.

Это очень легко проверить.

Для первой оценки сделайте что-нибудь вроде IF 1=0, а для второй сделайте что-нибудь, а затем покажите фактический план выполнения. В моем случае он выполняет только постоянное сканирование для оценки этих констант.

person JNK    schedule 04.04.2011
comment
Он работает точно так, как вы сказали в SQL Server, если я использую переменные, но я не думаю, что он работает, если вы используете EXISTS (SELECT ...). Я обнаружил, что SQL Server выполняет оба запроса. Я не знаю, как остановить SQL Server, чтобы прекратить выполнение обоих, не разбивая мои операторы на несколько IF. - person Krish; 04.04.2011
comment
@Krish - Как ты это проверяешь? Если я сделаю SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values) OR EXISTS(SELECT * from master..spt_monitor) PRINT 'Y', то я вижу, что был выполнен доступ только к одной таблице, несмотря на то, что обе они указаны в плане. - person Martin Smith; 05.04.2011
comment
Привет, Мартин, Спасибо за ответ. Как только я добавляю условие where к вашему запросу, план запроса меняется. SET STATISTICS IO ON IF EXISTS (ВЫБРАТЬ * из master..spt_values, где [name] = 'rpcc') или EXISTS (SELECT * from master..spt_monitor, где pack_sent = 5235252) ПЕЧАТЬ 'Y' - person Krish; 05.04.2011
comment
@Krish - Но оба эти условия неверны. Поэтому ему необходимо проверить оба, в каком бы порядке они ни выполнялись, чтобы убедиться, что другой является истинным. - person Martin Smith; 05.04.2011
comment
Мартин, SET STATISTICS IO ON IF EXISTS (SELECT * from master..spt_values, где [name] = 'rpcc') и EXISTS (SELECT * from master..spt_monitor, где pack_sent = 523525233333) PRINT 'Y', если я выполню этот запрос , оба запускаются, даже если оба запроса возвращают false. - person Krish; 05.04.2011
comment
@JNK - Ваш пример просто иллюстрирует обнаружение противоречия, а не короткое замыкание. - person Martin Smith; 06.04.2011
comment
IF EXISTS(subquery1) AND EXISTS(subquery2) никогда не похоже на короткое замыкание ... - person Martin Smith; 07.04.2011
comment
@Martin - я это вижу! Как странно. - person JNK; 07.04.2011

Вы можете предотвратить второе сканирование, выполнив следующие действия:

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
person herostwist    schedule 14.04.2011