@@FETCH STATUS: примеры (SQL)

Работа с функцией @@FETCH_STATUS в SQL Server
Раздел: Функции работы с курсорами
@@FETCH_STATUS: int

Функция @@FETCH_STATUS в MS SQL Server является системной функцией, которая возвращает статус последнего выполненного оператора FETCH в рамках текущего соединения. Она используется исключительно в контексте курсоров для контроля процесса выборки строк.

Основное применение функции связано с циклами, в которых выполняется обход строк курсора. После каждого вызова FETCH необходимо проверять значение @@FETCH_STATUS, чтобы определить, была ли выборка успешной или требуется завершить цикл.

Возвращаемые значения

  • 0: Оператор FETCH выполнен успешно, строка выбрана.
  • -1: Оператор FETCH завершился ошибкой, или достигнут конец результирующего набора. Это наиболее распространенное значение для выхода из цикла.
  • -2: Выбранная строка отсутствует (например, была удалена из набора данных после открытия курсора). Такая ситуация возможна только для динамических курсоров.

Функция не принимает параметров. Её состояние сбрасывается при каждом новом вызове FETCH. Важно учитывать, что статус относится именно к последнему выполненному FETCH в текущем сеансе, поэтому использование нескольких курсоров требует аккуратного подхода.

Простые примеры использования

Базовый пример с простым циклом:

DECLARE @Name NVARCHAR(50);
DECLARE myCursor CURSOR FOR
SELECT name FROM sys.objects WHERE type = 'U';
OPEN myCursor;
FETCH NEXT FROM myCursor INTO @Name;
WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT @Name;
    FETCH NEXT FROM myCursor INTO @Name;
END
CLOSE myCursor;
DEALLOCATE myCursor;
-- Вывод имен всех пользовательских таблиц в базе данных

Пример с обработкой всех статусов:

DECLARE @ID INT;
DECLARE cur CURSOR DYNAMIC FOR
SELECT object_id FROM sys.objects;
OPEN cur;
FETCH NEXT FROM cur INTO @ID;
WHILE 1=1
BEGIN
    IF @@FETCH_STATUS = 0
        PRINT 'Успешно: ' + CAST(@ID AS VARCHAR);
    ELSE IF @@FETCH_STATUS = -1
    BEGIN
        PRINT 'Конец данных';
        BREAK;
    END
    ELSE IF @@FETCH_STATUS = -2
        PRINT 'Строка пропущена';
    FETCH NEXT FROM cur INTO @ID;
END
CLOSE cur;
DEALLOCATE cur;
-- В зависимости от данных будет выводиться статус каждой выборки

Альтернативы в MS SQL

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

  • WHILE с идентификатором: Использование цикла по идентификаторам, когда возможно однозначно определить порядок.
  • Оконные функции: Позволяют выполнять вычисления над наборами строк без явного цикла.
  • CURSOR_STATUS: Функция, которая возвращает статус курсора, а не последней операции FETCH. Может быть полезной для проверки состояния курсора перед операциями.

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

Аналоги в других СУБД

В других базах данных существуют схожие механизмы:

  • Oracle (PL/SQL): Используются атрибуты %FOUND, %NOTFOUND, %ROWCOUNT курсора. Пример:
    DECLARE
      CURSOR cur IS SELECT * FROM employees;
      emp employees%ROWTYPE;
    BEGIN
      OPEN cur;
      LOOP
        FETCH cur INTO emp;
        EXIT WHEN cur%NOTFOUND;
        -- обработка
      END LOOP;
      CLOSE cur;
    END;
  • PostgreSQL (PL/pgSQL): Используется специальная переменная FOUND. Пример:
    DECLARE
      row record;
    BEGIN
      FOR row IN SELECT * FROM table_name LOOP
        -- обработка
      END LOOP;
      IF NOT FOUND THEN
        RAISE NOTICE 'Данные не найдены';
      END IF;
    END;
  • MySQL: В процедурах используется условие NOT FOUND для обработчиков. Пример:
    DECLARE done INT DEFAULT FALSE;
    DECLARE cur CURSOR FOR SELECT col FROM table;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    OPEN cur;
    loop1: LOOP
      FETCH cur INTO var;
      IF done THEN LEAVE loop1;
      END IF;
      -- обработка
    END LOOP;
    CLOSE cur;

Типичные ошибки

1. Проверка статуса до первого FETCH: Значение функции не определено до выполнения первой выборки.

DECLARE @id INT;
DECLARE cur CURSOR FOR SELECT id FROM table;
OPEN cur;
-- Ошибка: статус относится к предыдущему курсору, которого нет
IF @@FETCH_STATUS = -1
    PRINT 'Курсор пуст'; -- Это не будет корректно
FETCH NEXT FROM cur INTO @id;
-- Сообщение об ошибке не выводится, но логика нарушена
2. Использование одного статуса для нескольких курсоров: Функция глобальна для сеанса.
DECLARE @name1 NVARCHAR(50), @name2 NVARCHAR(50);
DECLARE cur1 CURSOR FOR SELECT name FROM table1;
DECLARE cur2 CURSOR FOR SELECT name FROM table2;
OPEN cur1;
OPEN cur2;
FETCH NEXT FROM cur1 INTO @name1;
FETCH NEXT FROM cur2 INTO @name2;
-- @@FETCH_STATUS теперь отражает статус только последнего FETCH из cur2
WHILE @@FETCH_STATUS = 0 -- Это проверка только для cur2
BEGIN
    -- Обработка
    FETCH NEXT FROM cur1 INTO @name1; -- Ошибка логики
END

Изменения в последних версиях

Функция @@FETCH_STATUS не претерпела существенных изменений в последних версиях MS SQL Server. Её поведение остается стабильным и обратно совместимым. В документации Microsoft SQL Server 2019 и 2022 годов указаны те же возвращаемые значения и условия использования.

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

Расширенные примеры

Использование в процедуре с обработкой ошибок и динамическим SQL:

Пример sql
CREATE PROCEDURE ProcessTables
AS
BEGIN
    DECLARE @TableName SYSNAME;
    DECLARE @Sql NVARCHAR(MAX);
    DECLARE tableCursor CURSOR LOCAL FAST_FORWARD FOR
        SELECT name FROM sys.tables WHERE is_ms_shipped = 0;
    OPEN tableCursor;
    FETCH NEXT FROM tableCursor INTO @TableName;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        BEGIN TRY
            SET @Sql = 'UPDATE ' + QUOTENAME(@TableName) + 
                       ' SET ModifiedDate = GETDATE() WHERE ModifiedDate IS NULL';
            EXEC sp_executesql @Sql;
            PRINT 'Обработана таблица: ' + @TableName;
        END TRY
        BEGIN CATCH
            PRINT 'Ошибка в таблице ' + @TableName + ': ' + ERROR_MESSAGE();
        END CATCH
        FETCH NEXT FROM tableCursor INTO @TableName;
    END
    CLOSE tableCursor;
    DEALLOCATE tableCursor;
END;

Пример с вложенными курсорами:

Пример sql
DECLARE @CategoryID INT, @ProductName NVARCHAR(50);
DECLARE @Message NVARCHAR(MAX);
DECLARE categoryCursor CURSOR FOR
    SELECT CategoryID FROM Categories;
OPEN categoryCursor;
FETCH NEXT FROM categoryCursor INTO @CategoryID;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @Message = 'Категория: ' + CAST(@CategoryID AS NVARCHAR);
    PRINT @Message;
    DECLARE productCursor CURSOR FOR
        SELECT ProductName FROM Products WHERE CategoryID = @CategoryID;
    OPEN productCursor;
    FETCH NEXT FROM productCursor INTO @ProductName;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT '  Продукт: ' + @ProductName;
        FETCH NEXT FROM productCursor INTO @ProductName;
    END
    CLOSE productCursor;
    DEALLOCATE productCursor;
    FETCH NEXT FROM categoryCursor INTO @CategoryID;
END
CLOSE categoryCursor;
DEALLOCATE categoryCursor;

MS SQL @@FETCH_STATUS function comments

En
@@FETCH STATUS Returns the status of the last cursor FETCH statement issued against any cursor currently opened by the connection