Очистка курсоров без спагетти и дублированного кода?

Я использую GoTo для закрытия и освобождения курсора. Есть ли лучший способ написать мой код? Обратите внимание: этот код предназначен для изучения того, как использовать курсоры и выходные параметры.

Я новичок в TSQL, поэтому не стесняйтесь комментировать другие темы. Например, я искал хранимую процедуру, которая закроет курсор, если он открыт, но я ничего не нашел через поиск Google.

Спасибо

-- Find the MAX, MIN, Avg for each Color in the Products table using Cursor

USE [AdventureWorks2019]
GO
/****** Object:  StoredProcedure [dbo].[WithCursor]    Script Date: 5/29/2024 11:30:17 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER     PROC [dbo].[WithCursor]
(
    @color VARCHAR(20) = NULL,
    @MinStock INT OUTPUT,
    @MaxStock INT OUTPUT,
    @AvgStock INT OUTPUT
)
AS

SET NOCOUNT ON

BEGIN TRY
    
    Declare @recordCount int = ( SELECT Count(*) 
        FROM Production.Product 
        WHERE (@color IS NULL AND Color IS NULL) OR (Color = @color))

    -- ******************** NO Records Exist ********************
    IF @recordCount = 0
    Begin

        DECLARE @errorMessage NVARCHAR(200);
        IF @color IS NULL
            SET @errorMessage = N'There are no products with the color NULL';
        ELSE
            SET @errorMessage = CONCAT(N'There are no products with the color ', @color);

        RAISERROR(@errorMessage, 11, 1);
        RETURN -2; -- no such color

    End

    -- ******************** Open the Cursor for Products ********************
    DECLARE @level int;
    IF EXISTS (
        SELECT 1 
        FROM sys.dm_exec_cursors(0) 
        WHERE name = 'pCursor'
    )
    BEGIN
        CLOSE pCursor
        DEALLOCATE ProductCursor;
    END

    DECLARE pCursor CURSOR FOR 
            SELECT SafetyStockLevel
            FROM Production.Product
            WHERE (@color IS NULL AND Color IS NULL) OR (Color = @color);
        
    --Open the Cursor.  We know there is at least 1 record from the above check.
    OPEN pCursor  
    FETCH NEXT FROM pCursor INTO @level

    -- ******************** 1 Record Exist ********************
    IF @recordCount = 1
    Begin

        Set @MinStock = @level
        Set @MaxStock = @level
        Set @AvgStock = @level

        GoTo Cleanup

    End
    -- ******************** 2 or More Records Exist ********************
    
    Declare @SumLevel int
    Declare @MinLevel int
    Declare @MaxLevel int

    Set @SumLevel = @level
    Set @MinLevel = @level
    Set @MaxLevel = @level

    --Move to the second record
    FETCH NEXT FROM pCursor  INTO @level

    While @@FETCH_STATUS = 0
    Begin
        
        Set @SumLevel = @SumLevel + @level

        If @level < @MinLevel
            Set @MinLevel = @level

        If @level > @MaxLevel
            Set @MaxLevel = @level

        FETCH NEXT FROM pCursor  INTO @level
    End
        
    
    Set @MinStock = @Minlevel
    Set @MaxStock = @Maxlevel
    Set @AvgStock = @SumLevel / @recordCount
    
   GoTo Cleanup

END TRY

BEGIN CATCH
    THROW;
    
    --Cleanup
    IF EXISTS (
        SELECT 1 
        FROM sys.dm_exec_cursors(0) 
        WHERE name = 'pCursor'
    )
    BEGIN
        CLOSE pCursor
        DEALLOCATE ProductCursor;
    END
    
    RETURN -1;
END CATCH;

--Success
Cleanup:
CLOSE pCursor
DEALLOCATE pCursor
Return 0

Я могу продублировать код, и это удалит GoTo, но я стараюсь максимально не дублировать код.

Вместо этого вы можете задать свой вопрос в Code Review Stack Exchange.

blurfus 29.05.2024 19:25

Случайные советы: (@color IS NULL AND Color IS NULL) OR (Color = @color) может быть @color is not distinct from Color в SQL Server 2022. N'There are no products with the color ' + Coalesce( @color, N'NULL' ). Добавьте терминаторы операторов (;).

HABO 29.05.2024 20:26

Мои мысли: (1) Я бы отказался от первоначального @RowCount расчета и начального автономного FETCH NEXT. Просто просматривайте строки по одной, считая вхождения. Если после цикла счетчик равен нулю, выполните специальное действие. (2) Я никогда не утруждаюсь проверкой и освобождением существующего курсора заранее. Я просто предполагаю, что система находится в чистом состоянии для запуска. (Исключением может быть отладка и выборочное выполнение фрагментов кода.) (3) Если вы перехватываете исключение, возможно, вы можете просто установить флаг, перейти к логике очистки и, наконец, выполнить соответствующее действие возврата результата.

T N 29.05.2024 21:39

Если ProductCursor должно отличаться от pCursor?

T N 29.05.2024 21:39

Спасибо за ответы. Blufus: есть ли способ автоматически перенести этот тред в Code Review? Хабо: Спасибо. К сожалению, я использую SQL Server 2019, но включил N'Нет продуктов с цветом ' + Coalesce(@color, N'NULL'). ТН: Спасибо за замечание о ProductCursor. Почему SQL Server не создал ошибку? Я подумаю над остальными вашими пунктами.

Picard78 29.05.2024 22:16

Я опубликовал решение ниже. В блоке Catch, когда я бросаю перед возвратом -1, возврат не выполняется, и наоборот. Как лучше всего выбирать место проведения Броска? Спасибо

Picard78 29.05.2024 23:07

@TN Я почти уверен, что они имеют в виду только форму DECLARE @c CURSOR, а не форму DECLARE c CURSOR, но, возможно, это можно было бы сформулировать лучше.

testing-for-ya 30.05.2024 14:41

@testing-ma-lady — Хороший улов. Ты прав. Я не заметил разницы между «курсором» и «переменной курсора». Тем не менее, с логикой броска происходит что-то сложное. Если я закомментирую операторы закрытия/освобождения курсора, курсор останется после обычного выхода, но исчезнет после повторного THROW, который перехватывается вызывающей стороной. Посмотрите эту скрипку. Для 3-го и 4-го (ультрафиолетового и черного) тестов курсор пропадает между «Здесь 2б» и «Здесь 3а», и я не понимаю почему. (Именно тогда я искал и наткнулся на этот оператор «неявно освобожденного».)

T N 30.05.2024 20:55
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я провел рефакторинг кода с учетом всех предложений. Всем спасибо. Было очень полезно прочитать ваш отзыв. В дальнейшем я буду использовать локальные курсоры.

    USE [AdventureWorks2019];
GO
/****** Object:  StoredProcedure [dbo].[WithCursor]    Script Date: 5/29/2024 11:30:17 AM ******/
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

CREATE OR ALTER PROC [dbo].[WithCursor]
(
    @color VARCHAR(20) = NULL,
    @MinStock INT OUTPUT,
    @MaxStock INT OUTPUT,
    @AvgStock INT OUTPUT
)
AS

SET NOCOUNT ON;

BEGIN TRY

    -- ******************** Open the Cursor for Products ********************

    DECLARE @level INT;

    --Local Cursor doesn't need to be deallocated
    DECLARE @pCursor CURSOR;

    SET @pCursor = CURSOR READ_ONLY FORWARD_ONLY
      FOR 
        SELECT SafetyStockLevel
          FROM Production.Product
         WHERE Color = @color OR (@Color IS NULL AND Color IS NULL);

    -- Open the Cursor. 
    OPEN @pCursor;
    FETCH NEXT FROM @pCursor INTO @level;

    -- ******************** Empty Recordset ********************
    IF @@FETCH_STATUS <> 0
    BEGIN 
        DECLARE @errorMessage NVARCHAR(200);
        SET @errorMessage = N'There are no products with the color ' + COALESCE(@color, N'NULL');
        RAISERROR(@errorMessage, 11, 1);
        RETURN -2; -- no such color
    END;

    -- ******************** 1 or More Records Exist ********************

    DECLARE @SumLevel INT = 0;
    DECLARE @MinLevel INT = @level;
    DECLARE @MaxLevel INT = @level;
    DECLARE @recordCount INT = 0;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @SumLevel = @SumLevel + @level;

        IF @level < @MinLevel
            SET @MinLevel = @level;

        IF @level > @MaxLevel
            SET @MaxLevel = @level;

        FETCH NEXT FROM @pCursor INTO @level;

        SET @recordCount = @recordCount + 1;
    END;

    --Set the Output Parameters
    SET @MinStock = @MinLevel;
    SET @MaxStock = @MaxLevel;
    SET @AvgStock = @SumLevel / @recordCount;


END TRY

BEGIN CATCH

    THROW;
    RETURN -1;

END CATCH;

Также помните о различиях между ЛОКАЛЬНЫМИ и ГЛОБАЛЬНЫМИ курсорами. Обычно рекомендуется использовать ЛОКАЛЬНЫЙ курсор, чтобы избежать таких ошибок, как: «Курсор с именем «YourCursorName» уже существует». Если вы не укажете явно, обычно по умолчанию используется значение GLOBAL (зависит от параметров базы данных).

Moe Sisko 30.05.2024 02:04

Благодарим вас за вклад в сообщество Stack Overflow. Возможно, это правильный ответ, но было бы очень полезно предоставить дополнительные пояснения к вашему коду, чтобы разработчики могли понять ваши рассуждения. Это особенно полезно для новых разработчиков, которые не так хорошо знакомы с синтаксисом или пытаются понять концепции. Не могли бы вы отредактировать свой ответ, включив в него дополнительную информацию на благо сообщества?

Jeremy Caney 30.05.2024 03:10

Другие вопросы по теме

Похожие вопросы

Рефакторинг T-SQL в Databricks SQL для возврата результатов из подзапроса
Заменить нулевые значения значениями случайных значений из таблицы
Использование OpenJson в операторе выбора
Внешнее объединение всей таблицы дат с агрегатом в бизнес-таблице
Дедупликация идентификаторов по двум столбцам
Общее выражение для поиска частичных совпадений строк с динамическим списком полей в запросе LINQ
SQL восстанавливает одну таблицу из резервной базы данных. Столбцы имеют метку времени, и я должен использовать IDENTITY_INSERT
SQL-скрипт, который будет корректировать значение следующей строки, когда будет обнаружено отрицательное значение
Возьмите данные из записи интереса, когда условие или критерии фильтра совпадают, и игнорируйте записи между текущей строкой и выбранной записью (строкой интереса)
Рассчитайте дату окончания строк раздела, используя дату начала следующего раздела