Как упорядочить повторяющийся код в хранимой процедуре SQL Server?

#sql #sql-server

#sql #sql-сервер

Вопрос:

У меня есть следующая хранимая процедура:

 IF EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'ChangeClientId') AND type in (N'P', N'PC'))
    DROP PROCEDURE ChangeClientId
GO

CREATE PROCEDURE [dbo].[ChangeClientId] 
    (@dst SYSNAME,
     @NewClientId INT,
     @dryRun BIT = 1)
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @TableOfSqls TABLE (Id INT IDENTITY PRIMARY KEY, Part INT, Sql NVARCHAR(MAX))
    DECLARE @Phase INT = 0
    ...

    INSERT INTO @TableOfSqls VALUES (0, 'TRUNCATE TABLE #Failure')
    INSERT INTO @TableOfSqls VALUES (0, 'GO')

    SET @Phase = @Phase   1

    INSERT INTO @TableOfSqls VALUES (@Phase, '')
    INSERT INTO @TableOfSqls VALUES (@Phase, 'USE ['   @dst   ']')
    INSERT INTO @TableOfSqls VALUES (@Phase, 'SET XACT_ABORT ON')
    INSERT INTO @TableOfSqls VALUES (0, 'IF EXISTS (SELECT 1 FROM #Failure) THROW 50000, ''Skipped due to previous errors'', 0')
    INSERT INTO @TableOfSqls VALUES (0, 'INSERT INTO #Failure VALUES (0)')

    ...
END
 

Этот фрагмент кода

     INSERT INTO @TableOfSqls VALUES (0, 'TRUNCATE TABLE #Failure')
    INSERT INTO @TableOfSqls VALUES (0, 'GO')

    SET @Phase = @Phase   1

    INSERT INTO @TableOfSqls VALUES (@Phase, '')
    INSERT INTO @TableOfSqls VALUES (@Phase, 'USE ['   @dst   ']')
    INSERT INTO @TableOfSqls VALUES (@Phase, 'SET XACT_ABORT ON')
    INSERT INTO @TableOfSqls VALUES (0, 'IF EXISTS (SELECT 1 FROM #Failure) THROW 50000, ''Skipped due to previous errors'', 0')
    INSERT INTO @TableOfSqls VALUES (0, 'INSERT INTO #Failure VALUES (0)')
 

повторяется несколько раз в хранимой процедуре. В C # я бы поместил его во вложенную функцию. В T-SQL я должен иметь возможность создать временную хранимую процедуру или что-то в этом роде, но я не уверен.

Итак, мой вопрос — каков наилучший способ упорядочить этот повторяющийся код, чтобы уменьшить беспорядок?

Обоснование

Эта хранимая процедура генерирует большой динамический SQL и выполняет его. Однако он поддерживает пробный запуск, когда SQL отображается, но не запускается. Итак, процедура собирает все операторы SQL в таблицу, а затем либо выводит ее, либо составляет строку SQL и выполняет ее. Однако есть одна загвоздка, она выполняется поэтапно. Каждая фаза разделяется описанным фрагментом кода, который повторяется, но не идентичен, потому @Phase что увеличивается в середине.

Комментарии:

1. Один из методов заключается в создании другой хранимой процедуры для этой части кода.

2. Почему так много отдельных INSERT INTO операторов? Почему бы не выполнить их в одном операторе?

3. Кроме того, приведенное выше широко доступно для внедрения; никогда не вводите неподтвержденную строку в динамический оператор. Всегда правильно цитируйте свои динамические объекты с QUOTENAME помощью .

4. Это не производственный код, поэтому мне все равно, но вы правы.

5. Никогда не бывает причин не заботиться… Никогда не используйте код с уязвимостями для инъекций.

Ответ №1:

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

Вот код:

 ...
    DECLARE @Phase INT = 0

    SET @Sql = '
    CREATE PROCEDURE #ChangeClientIdSqlHelper(@Phase INT)
    AS
    BEGIN
        DECLARE @TableOfSqls TABLE (Id INT IDENTITY PRIMARY KEY, Part INT, Sql NVARCHAR(MAX))
        INSERT INTO @TableOfSqls VALUES (0, '''')
        INSERT INTO @TableOfSqls VALUES (0, ''TRUNCATE TABLE #Failure'')
        INSERT INTO @TableOfSqls VALUES (0, ''GO'')
        INSERT INTO @TableOfSqls VALUES (@Phase, '''')
        INSERT INTO @TableOfSqls VALUES (@Phase, ''USE '   QUOTENAME(@dst)   ''')
        INSERT INTO @TableOfSqls VALUES (@Phase, ''SET XACT_ABORT ON'')
        INSERT INTO @TableOfSqls VALUES (0, ''IF EXISTS (SELECT 1 FROM #Failure) THROW 50000, ''''Skipped due to previous errors'''', 0'')
        INSERT INTO @TableOfSqls VALUES (0, ''INSERT INTO #Failure VALUES (0)'')
        INSERT INTO @TableOfSqls VALUES (0, '''')
        SELECT Part, Sql FROM @TableOfSqls ORDER BY Id
    END
    '
    EXEC(@Sql)
...
    SET @Phase = @Phase   1
    INSERT INTO @TableOfSqls EXEC #ChangeClientIdSqlHelper @Phase
...
    SET @Phase = @Phase   1
    INSERT INTO @TableOfSqls EXEC #ChangeClientIdSqlHelper @Phase
...
 

Ответ №2:

Для ваших целей вы можете определить вторую временную таблицу:

 DECLARE @Repeats TABLE (Id INT IDENTITY PRIMARY KEY, Part INT, Sql NVARCHAR(MAX))

INSERT INTO @Repeats(Part, SQL) VALUES (@Phase, '')
INSERT INTO @Repeats(Part, SQL) VALUES (@Phase, 'USE ['   @dst   ']')
INSERT INTO @Repeats(Part, SQL) VALUES (@Phase, 'SET XACT_ABORT ON')
INSERT INTO @Repeats(Part, SQL) VALUES (0, 'IF EXISTS (SELECT 1 FROM #Failure) THROW 50000, ''Skipped due to previous errors'', 0')
INSERT INTO @Repeats(Part, SQL) VALUES (0, 'INSERT INTO #Failure VALUES (0)')
 

Затем вы можете вставить это, когда захотите:

 INSERT INTO @TableOfSqls (sql)
    SELECT part, sql
    FROM @Repeats
    ORDER BY id;
 

Я собираюсь добавить, что разбиение SQL построчно кажется любопытным подходом к написанию кода. Я не уверен на 100%, что динамический SQL вообще необходим. Если это так, вы можете сохранить весь блок кода (вместе с символами новой строки) в одной строке.

Комментарии:

1. Обратите внимание, что SET @Phase = @Phase 1 в середине

2. Учитывая, что @Phase увеличивается в каждом новом фрагменте, это предложение не работает. Есть еще идеи?

3. @mark . , , Вы можете увеличивать значение @phase при вставке новых строк, а не считывать его из таблицы.

4. Я не считываю его из таблицы. @Phase увеличивается мной в каждом фрагменте. Но я не могу использовать одну и ту же таблицу повторов, потому что она будет фиксировать начальное @Phase значение, равное 1.