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

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

13 голосов
/ 04 апреля 2011

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

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

END

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

Спасибо, Криш

Ответы [ 7 ]

11 голосов
/ 05 апреля 2011

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

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
*/
7 голосов
/ 04 апреля 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
2 голосов
/ 12 апреля 2011

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

Как SQL Server замыкает накоротко ГДЕ оценка состояния

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

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

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

Имеет ли SQL Server короткое замыкание?

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

2 голосов
/ 05 апреля 2011

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

SET STATISTICS IO ON IF EXISTS (SELECT * от master..spt_values ​​где [name] = 'rpcc') и EXISTS (SELECT * от master..spt_monitor где pack_sent = 5235252) PRINT 'Y'

Таблица 'spt_monitor'. Сканирование 1, логическое чтение 1, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0. Таблица «spt_values». Сканирование 1, логическое чтение 17, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.

1 голос
/ 05 апреля 2011

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

Однако в обоих случаях фактический план выполнения показывает поиски как для таблицы, так и для таблицы.TBLB.Это кажется странным для меня.Есть мысли по этому поводу?

0 голосов
/ 14 апреля 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
0 голосов
/ 04 апреля 2011

Нет.

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

Это очень легкоtest.

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

...