@@FETCH STATUS: примеры (SQL)
@@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:
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;Пример с вложенными курсорами:
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;