Подтвердить что ты не робот

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

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

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

END

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

Спасибо Криш

4b9b3361

Ответ 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
*/

Ответ 2

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

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

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

IF EXISTS(SELECT...) BEGIN
  IF EXISTS(SELECT...) BEGIN
    ...
  END
END

Ответ 3

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

НАСТРОЙКА СТАТИСТИКИ IO ON IF EXISTS (SELECT * from master..spt_values, где [name] = 'rpcc') и EXISTS (SELECT * из master..spt_monitor, где pack_sent = 5235252) PRINT 'Y'

Таблица 'spt_monitor'. Число сканирования 1, логическое чтение 1, физическое чтение 0, чтение вперед 0, логическое чтение лоб 0, физическое чтение lob 0, чтение с чтением lob 0. Таблица "spt_values". Число сканирования 1, логическое чтение 17, физическое считывание 0, чтение вперед 0, логическое считывание логических чисел 0, физическое чтение lob 0, чтение с чтением lob 0.

Ответ 4

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

Как оценка условий короткого замыкания SQL Server WHERE

Он делает это, когда ему кажется, но не так, как вы сразу думаете.

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

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

Есть ли короткое замыкание в SQL Server?

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

Ответ 5

Было интересное наблюдение. У меня есть две таблицы 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)

тогда доступны обе таблицы. Это довольно очевидно, так как здесь оптимизатор знает, что он должен проверить в обоих местах, чтобы убедиться, что условие И удовлетворено.

Однако в обоих случаях фактический план выполнения показывает запросы как на tbla, так и на tblb. Мне это кажется странным. Любые мысли об этом?

Ответ 6

Нет.

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

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

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

Ответ 7

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

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