Порядок выполнения в SQL-запросе? - PullRequest
0 голосов
/ 17 июля 2010

Следующий запрос является надуманным примером, который демонстрирует ошибку, которую я обнаружил в хранимой процедуре на этой неделе.

CREATE TABLE #temp
(
    ID int IDENTITY(1,1),
    Value char(1)
)

INSERT INTO #temp(Value) Values('a')
INSERT INTO #temp(Value) Values('b')
INSERT INTO #temp(Value) Values('c')
INSERT INTO #temp(Value) Values('d')

DECLARE
    @i int,
    @ID int,
    @Count int,
    @Value char(1)

SELECT @Count = COUNT(*) FROM #temp
SET @i = 1
SET @ID = 2

WHILE @i < @Count
BEGIN
    SELECT 
        @ID = ID,
        @Value = (SELECT Value FROM #temp WHERE ID = @ID)
    FROM
        #temp
    WHERE
        @i = ID

    PRINT @Value

    SET @i = @i + 1
END

На первый взгляд результат должен быть a b c d, но это не так!Это b b c d.Таким образом, порядок выполнения внутри оператора не тот, который мы могли бы предположить.

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

Ответы [ 6 ]

4 голосов
/ 17 июля 2010

Предложение WHERE в строке

@Value = (SELECT Value FROM #temp WHERE ID = @ID)

не связано с предложением WHERE здесь

    #temp
WHERE
    @i = ID

Итак, 1-й цикл

  • @ i =1, @ID = 2
  • В #temp, @ID = 2, поэтому вы получаете b
  • Затем вы назначаете @ID с 1

2-й цикл

  • @ i = 2, @ID = 1
  • В #temp, @ID = 1, чтобы вы получили a
  • Затем вы назначаете @ID с2

3-й цикл

  • @ i = 3, @ID = 2
  • В #temp, @ID = 2, поэтому вы получите b
  • Затем вы присваиваете @ID с помощью 3

Затем он останавливается из-за @i < @count

В SQL нет «порядка выполнения» как такового, потому что он декларативный.Предложение SELECT оценивается за один раз, не ожидается, что @ID будет назначен до его использования в следующей строке.

2 голосов
/ 17 июля 2010

Я удивлен, что он печатает четыре буквы в вашей системе, @i < @Count должен ограничить вывод до 3 строк.

Обычно вы можете полагаться на назначения столбцов, которые оцениваются по порядку.Но подзапрос может быть оценен в другом порядке (или даже преобразован в объединение для эффективности.) Если вы отделяете назначения от запроса, используя подзапрос:

SELECT  @ID = ID
,       @Value = TheVal
FROM    (
        SELECT  ID
        ,       (SELECT Value FROM #temp WHERE ID = @ID) as TheVal
        FROM    #temp
        WHERE   @i = ID
        ) sub

Результат должен быть надежным.Здесь это b a b, потому что оно начинается с @id = 2, а затем увеличивается с каждым циклом после обновления значения TheVal.

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

1 голос
/ 14 марта 2011

На самом деле, как я недавно выяснил, существует Порядок логической обработки (хотя и не гарантированный порядок физического выполнения - так как он основан на обработчике запросов)

Ссылка: http://msdn.microsoft.com/en-us/library/ms189499.aspx

Порядок логической обработки оператора SELECT

Следующие шаги показывают порядок логической обработки или порядок привязки для оператора SELECT.Этот порядок определяет, когда объекты, определенные на одном шаге, становятся доступными для предложений на последующих шагах.Например, если обработчик запросов может связать (получить доступ) к таблицам или представлениям, определенным в предложении FROM, эти объекты и их столбцы становятся доступными для всех последующих шагов.И наоборот, поскольку предложение SELECT является шагом 8, любые псевдонимы столбцов или производные столбцы, определенные в этом предложении, не могут быть указаны в предыдущих предложениях.Однако на них могут ссылаться последующие пункты, такие как предложение ORDER BY. Обратите внимание, что фактическое физическое выполнение оператора определяется обработчиком запросов, и порядок может отличаться от этого списка.(выделение мое)

  1. ОТ
  2. ВКЛ
  3. ПРИСОЕДИНИТЬСЯ
  4. ГДЕ
  5. GROUP BY
  6. С КУБОМ или С РОЛЛУПОМ
  7. ИМЕЯ
  8. ВЫБРАТЬ
  9. ОТЛИЧИТЬ
  10. ЗАКАЗАТЬ
  11. ТОП
1 голос
/ 17 июля 2010

Давайте посмотрим ближе на ваш запрос:

  1. Первый запуск:

    ВЫБРАТЬ @ID = ID, @Value = (ВЫБЕРИТЕ Значение ОТ #temp WHERE ID = @ID) ОТ #temp ГДЕ @i = ID

Здесь

 @i=1
   @ID=2

так после

 @Value = (SELECT Value FROM #temp WHERE ID = @ID)

получаем:

 @Value = 'b'

и значение @ID будет равно 1 из-за @ ID = ID, так как ID = 1 - единственное доступное значение после

WHERE @i = ID

выполнено.

  1. Второй прогон

    @ я = 2 @ ID = 1

Этот прогон напечатает

'а'

3 , Третий забег @ ID = 3 @ ID = 2

этот прогон напечатает

'б'

Тогда цикл завершается!

вот почему вы получили 'b' 'a' 'b'

1 голос
/ 17 июля 2010

В SQL все выражения, которые появляются на одной и той же фазе обработки логического запроса, оцениваются как в один и тот же момент времени.

Я имею в виду, что мы не можем сказать, что сначала будет оцениваться @ID= ID, а затем

@Value = (SELECT Value FROM #temp WHERE ID = @ID).

Кроме того, порядок выполнения пунктов:

ОТ

ГДЕ

GROUP BY

ЕСТЬ

ВЫБРАТЬ

ЗАКАЗАТЬ ПО

Именно поэтому на псевдонимы предложения SELECT можно ссылаться только в предложении ORDER BY.

Приветствие.

0 голосов
/ 17 июля 2010

Не уверен, чего вы пытаетесь достичь, и как вы получаете свои результаты.

Я вставил ваш код в MSSQL 2008 и получил

bab

Во-первых, у вас есть сравнение «меньше, чем счет», в результате чего получается только 3 записи.

Далее у вас есть два противоречащих предложения WHERE, одно с идентификатором, а другое с i.SELECT обновляет идентификатор.

Удалите подвыбор и измените на <= @Count, и вы должны получить «abcd» </p>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...