Sql while: MS SQL Server и T-SQL

Цикл SQL Server WHILE запускается дважды с обновлением, один раз без

Задавать вопрос

спросил

Изменено
1 год, 4 месяца назад

Просмотрено
1к раз

Вот это странно. Вот мой код:

 ПЕЧАТЬ 'Определить курсор'
ОБЪЯВИТЬ cursor1 КУРСОР ДЛЯ
ВЫБЕРИТЕ b.EmploymentTypeID
FROM EmploymentTypes b INNER JOIN #ListEmployments l
        на b.EmploymentID = l.EmploymentID
PRINT 'Открыть курсор'
ОТКРЫТЬ курсор1
FETCH NEXT FROM cursor1 INTO @pEmploymentTypeID
ПОКА @@FETCH_STATUS = 0
НАЧИНАТЬ
 PRINT '1) Внутри цикла WHILE. @pEmploymentTypeID: ' + convert(varchar(20), @pEmploymentTypeID)
 PRINT '2) Внутри цикла WHILE. @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
 -- поработай немного
 ОБНОВЛЕНИЕ
       SETEmploymentTypeRD = EmploymentTypeID
    ГДЕEmploymentTypeID = @pEmploymentTypeID
      И тип занятостиRD = 0
 PRINT '3) Внутри цикла WHILE.  Pre-FETCH @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
 FETCH NEXT FROM cursor1 INTO @pEmploymentTypeID
 PRINT '4) Внутри цикла WHILE. Пост-FETCH @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
  
КОНЕЦ
ЗАКРЫТЬ курсор1
УДАЛИТЬ курсор1
 

Когда я запускаю это, цикл выполняется дважды, и я вижу следующий вывод.

 Определить курсор
Открытый курсор
1) Внутри цикла WHILE. @pEmploymentTypeID: 695837
2) Внутри цикла WHILE. @@FETCH_STATUS: 0
3) Внутри цикла WHILE. Предварительная выборка @@FETCH_STATUS: 0
4) Внутри цикла WHILE. Пост-FETCH @@FETCH_STATUS: 0
1) Внутри цикла WHILE. @pEmploymentTypeID: 695837
2) Внутри цикла WHILE. @@FETCH_STATUS: 0
3) Внутри цикла WHILE. Предварительная выборка @@FETCH_STATUS: 0
4) Внутри цикла WHILE. Пост-FETCH @@FETCH_STATUS: -1
Убежал в ROLLBACK
 

Если я закомментирую оператор UPDATE после комментария «—поработайте», вывод будет следующим:

 Определить курсор
Открытый курсор
1) Внутри цикла WHILE. @pEmploymentTypeID: 695837
2) Внутри цикла WHILE.  @@FETCH_STATUS: 0
3) Внутри цикла WHILE. Предварительная выборка @@FETCH_STATUS: 0
4) Внутри цикла WHILE. Пост-FETCH @@FETCH_STATUS: -1
Убежал в ROLLBACK
 

Почему цикл выполняется дважды при включенном обновлении?

  • sql-сервер

1

Вы столкнулись с проблемой, более известной как проблема Хэллоуина , другими словами, вы читаете только что измененные строки.

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

Вместо этого выполните простое совместное обновление. Это одиночная инструкция , которая выполняет все в вашем скрипте. Сервер имеет специальную логику для учета изменяемых строк при чтении.

 ОБНОВЛЕНИЕ и др.
SETEmploymentTypeRD = EmploymentTypeID
ОТ EmploymentTypes et
ВНУТРЕННЕЕ СОЕДИНЕНИЕ #ListEmployments l ON et.EmploymentID = l.EmploymentID
ГДЕ et.EmploymentTypeRD = 0;
 

Для этого лучше всего подойдет операция на основе SET. Однако, если вы должны использовать курсор или просто хотите узнать, почему он работает не так, как вы ожидаете…

Вы обновляете строки базовых таблиц, используемых курсором, а затем заставляете его возвращать дополнительную строку после первого обновления. Обновление во время второго цикла ничего не делает, потому что EmploymentTypeRD больше не равен 0, значит, курсор существует. Попробуйте добавить аргумент STATIC к объявлению курсора. Это кэширует результаты курсора во временную таблицу в том состоянии, в котором они существовали до начала цикла и выполнения любых обновлений.

Просмотрите документацию Microsoft по аргументам курсора здесь: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-cursor-transact-sql?view=sql-server-ver15

Ниже приведен измененный код.

 ПЕЧАТЬ 'Определить курсор'
ОБЪЯВИТЬ cursor1 CURSOR **STATIC** ДЛЯ
ВЫБЕРИТЕ b.EmploymentTypeID
FROM EmploymentTypes b INNER JOIN #ListEmployments l
        на b.EmploymentID = l. EmploymentID
PRINT 'Открыть курсор'
ОТКРЫТЬ курсор1
FETCH NEXT FROM cursor1 INTO @pEmploymentTypeID
ПОКА @@FETCH_STATUS = 0
НАЧИНАТЬ
 PRINT '1) Внутри цикла WHILE. @pEmploymentTypeID: ' + convert(varchar(20), @pEmploymentTypeID)
 PRINT '2) Внутри цикла WHILE. @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
 -- поработай немного
 ОБНОВЛЕНИЕ
       SETEmploymentTypeRD = EmploymentTypeID
    ГДЕEmploymentTypeID = @pEmploymentTypeID
      И тип занятостиRD = 0
 PRINT '3) Внутри цикла WHILE. Pre-FETCH @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
 FETCH NEXT FROM cursor1 INTO @pEmploymentTypeID
 PRINT '4) Внутри цикла WHILE. Пост-FETCH @@FETCH_STATUS: ' + convert(varchar(20), @@FETCH_STATUS)
  
КОНЕЦ
ЗАКРЫТЬ курсор1
УДАЛИТЬ курсор1
 

1

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя электронную почту и пароль

Опубликовать как гость

Электронная почта

Требуется, но никогда не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Нажимая «Опубликовать свой ответ», вы соглашаетесь с нашими условиями обслуживания и подтверждаете, что прочитали и поняли нашу политику конфиденциальности и кодекс поведения.

Помощник SQL Server

Сообщения об ошибках SQL Server — сообщение 135

Сообщение об ошибке

 Сервер: сообщение 135, уровень 16, состояние 1, строка 1
Нельзя использовать оператор BREAK вне области действия
ПОКА заявление. 

Причины

Оператор BREAK выходит из самого внутреннего цикла в операторе WHILE или IF… ELSE. Любые операторы, идущие после ключевого слова END, отмечающего конец цикла, выполняются. BREAK часто, но не всегда, запускается условием проверки IF.

Как следует из сообщения, это сообщение об ошибке возникает при использовании инструкции BREAK вне инструкции WHILE. Вот несколько примеров возникновения этой ошибки:

 -- BREAK используется внутри условия IF, но вне инструкции WHILE
ЕСЛИ НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ «X» ИЗ [dbo]. [Пользователь]
               ГДЕ [имя пользователя] = 'sqlserver')
    ПЕРЕРЫВ
    
Сообщение 135, уровень 15, состояние 1, строка 2
Нельзя использовать инструкцию BREAK вне области действия инструкции WHILE.
 
 -- BREAK используется для выхода из хранимой процедуры
СОЗДАТЬ ПРОЦЕДУРУ [dbo].[usp_GetOrderDetails]
    @OrderID INT
КАК

ЕСЛИ НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ «X» ИЗ [dbo]. [Заказ]
               ГДЕ [IDЗаказа] = @IDЗаказа)
    ПЕРЕРЫВ

SELECT * FROM [dbo].[OrderDetail]
ГДЕ [IDЗаказа] = @IDЗаказа
ИДТИ

Сообщение 135, уровень 15, состояние 1, строка 2
Нельзя использовать инструкцию BREAK вне области действия инструкции WHILE.
 
 -- Неуместное условие BREAK
ОБЪЯВИТЬ @Counter INT
ОБЪЯВИТЬ @UserID INT

УСТАНОВИТЕ @Счетчик = 0
УСТАНОВИТЕ @UserID = 1
ПОКА СУЩЕСТВУЕТ (SELECT * FROM [dbo]. [UserTransaction]
              ГДЕ [UserID] = @UserID)
    УДАЛИТЬ TOP (10) FROM [dbo].[UserTransaction]
    ГДЕ [UserID] = @UserID

    SET @Counter = Счетчик + 1

    ЕСЛИ @Счетчик > 10
        ПЕРЕРЫВ


Сообщение 135, уровень 15, состояние 1, строка 2
Нельзя использовать инструкцию BREAK вне области действия инструкции WHILE.
 

Решение/Временное решение:

Как следует из сообщения, оператор BREAK можно использовать только в рамках оператора WHILE. В тех случаях, когда необходимо пропустить набор инструкций Transact-SQL, если определенное условие не выполняется, вместо использования инструкции BREAK можно использовать инструкцию GOTO. Оператор GOTO изменяет поток выполнения на метку. Оператор Transact-SQL или операторы, следующие за GOTO, пропускаются, и обработка продолжается с указанной метки. Операторы GOTO и метки могут использоваться в любом месте внутри процедуры, пакета или блока операторов, а также могут быть вложенными.

Используя первый пример ранее, вот как это будет выглядеть при замене инструкции BREAK на инструкцию GOTO:

, ЕСЛИ НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ «X» ИЗ [dbo]. [Пользователь]
               ГДЕ [имя пользователя] = 'sqlserver')
    ПЕРЕЙТИ к недействительному пользователю

/*
    Набор инструкций Transact-SQL для выполнения здесь
*/

Недействительный пользователь:

/*
    Другой набор операторов Transact-SQL для выполнения здесь
*/
 

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

Используя второй пример выше, вот как будет выглядеть хранимая процедура, заменяющая оператор BREAK оператором RETURN:

 СОЗДАТЬ ПРОЦЕДУРУ [dbo].