Динамический запрос в хранимой процедуре не возвращает ошибку, но не может вставить запись - PullRequest
0 голосов
/ 26 марта 2019

Я пытаюсь написать хранимую процедуру, которая будет вставлять записи, где column1 и column2 «фиксированы», а column3, column4 и column5 изменяются.Иногда нет даже column3, column4 или column5.

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

Вот мой код:

ALTER PROCEDURE [dbo].[usp_Insert]
    @param1 nvarchar(MAX) = NULL, 
    @param2 nvarchar(MAX) = NULL, 
    @param3 nvarchar(MAX) = NULL, --optional
    @param4 nvarchar(MAX) = NULL, --optional
    @param5 nvarchar(MAX) = NULL, --optional
    @col3 nvarchar(50) = NULL, --optional
    @col4 nvarchar(50) = NULL, --optional
    @col5 nvarchar(50) = NULL  --optional

AS

SET NOCOUNT ON;
DECLARE @QRY NVARCHAR(MAX) = '';

BEGIN
    IF NOT EXISTS (Select ...)
        BEGIN TRY
            BEGIN TRANSACTION
            SET @QRY = @QRY + ' DECLARE @param1 nvarchar(MAX) = ' + CHAR(39) + @param1 + CHAR(39) + ';'
            SET @QRY = @QRY + ' DECLARE @param2 nvarchar(MAX) = ' + CHAR(39) + @param2 + CHAR(39) + ';'
            IF @param3 is not null
                SET @QRY = @QRY + ' DECLARE @param3 nvarchar(MAX) = ' + CHAR(39) + @param3 + CHAR(39) + ';'
            IF @param4 is not null
                SET @QRY = @QRY + ' DECLARE @param4 nvarchar(MAX) = ' + CHAR(39) + @param4 + CHAR(39) + ';'
            IF @param5 is not null
                SET @QRY = @QRY + ' DECLARE @param5 nvarchar(MAX) = ' + CHAR(39) + @param5 + CHAR(39) + ';'

            SET @QRY = @QRY + ' INSERT INTO tableName (column1, column2' 
            IF @col3 is not null
                SET @QRY = @QRY + ', ' + @col3
            IF @col4 is not null
                SET @QRY = @QRY + ', ' + @col4
            IF @col5 is not null
                SET @QRY = @QRY + ', ' + @col5
            SET @QRY = @QRY + ') VALUES '
            SET @QRY = @QRY + ' (@param1, @param2 '
            IF @col3 is not null
                SET @QRY = @QRY + ', @param3'
            IF @col4 is not null
                SET @QRY = @QRY + ', @param4'
            IF @col5 is not null
                SET @QRY = @QRY + ', @param5'
            SET @QRY = @QRY + ')'

            EXEC (@QRY)

            COMMIT TRANSACTION
        END TRY
        BEGIN CATCH
            IF (@@TRANCOUNT > 0) 
                BEGIN  
                    ROLLBACK TRANSACTION   
                    RAISERROR('Exception occurred. Transaction rolled back.',16,1)
                END
        END CATCH
    ELSE
        BEGIN  
            RAISERROR('Duplicate record found. Record not inserted.',16,1)
        END
END

Если есть лучший способ добиться того, что я пытаюсь сделать, пожалуйста, просветите меня.Спасибо!

РЕДАКТИРОВАТЬ : Мне очень жаль за путаницу.Да, в моем коде есть Begin and Commit.Кроме того, имена столбцов столбцов 3, 4 и 5 также передаются в качестве параметров в хранимую процедуру.Вот почему я решил использовать динамический запрос.Я отредактировал код выше соответственно.Мне очень жаль, что я пропустил эту очень важную часть !!

Ответы [ 5 ]

1 голос
/ 26 марта 2019

Вы должны проверить свои параметры для NULL, посмотрите на этот пример

declare @param1 nvarchar(10) = null
declare @QRY nvarchar(100)

set @QRY = 'test ' + @param1

select @QRY

результат будет NULL, поскольку одно из значений конкатенации было нулевым

Таким образом, вы должны проверить наличие нуля и заменить его текстом «NULL» или другим значением
Если мы сделаем это, то наша конкатинация больше не будет NULL, посмотрите на этот пример еще раз

declare @param1 nvarchar(10) = null
declare @QRY nvarchar(100)

set @QRY = 'test ' + isnull(@param1, 'null')

select @QRY

это приведет к test null

Может быть, именно в этом ваша проблема

Поэтому я рекомендую изменить это на

SET @QRY = @QRY + ' DECLARE @param1 nvarchar(MAX) = ' + CHAR(39) + ISNULL(@param1, 'NULL') + CHAR(39) + ';'
SET @QRY = @QRY + ' DECLARE @param2 nvarchar(MAX) = ' + CHAR(39) + ISNULL(@param2, 'NULL') + CHAR(39) + ';
1 голос
/ 26 марта 2019

используйте sp_executesql и передайте параметры как параметры:

ALTER PROCEDURE [dbo].[usp_Insert]
    @param1 nvarchar(MAX) = NULL, 
    @param2 nvarchar(MAX) = NULL, 
    @param3 nvarchar(MAX) = NULL, --optional
AS

SET NOCOUNT ON;
DECLARE @QRY NVARCHAR(MAX);

BEGIN
    IF NOT EXISTS (Select ...)
            SET @QRY = N'INSERT INTO tableName (column1, column2' 
            IF @param3 is not null
                SET @QRY = @QRY + N', column3'
            SET @QRY = @QRY + N') VALUES '
            SET @QRY = @QRY + N' (@p1, @p2 '
            IF @param3 is not null
                SET @QRY = @QRY + N', @p3'
            SET @QRY = @QRY + N')'

            exec sp_executesql @QRY, N'@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX), @p3 NVARCHAR(MAX)', @param1, @param2, @param3;

    END
END

@param... - локальные сохраненные параметры процесса.@p1 ... @px - параметры динамического контекста SQL.Можно передавать дополнительные параметры, которые не используются (@p3 при NULL).

Также неверна обработка транзакции BEGIN TRY ... CATCH.См. http://rusanu.com/2009/06/11/exception-handling-and-nested-transactions/ для правильного шаблона.

1 голос
/ 26 марта 2019

Одна проблема, которую я вижу (благодаря @GuidoG) заключается в том, что если @param1 или @param2 равны NULL, то ваша @QRY строка будет NULL.

Вы можете заменить эти строки ...

        SET @QRY = @QRY + ' DECLARE @param1 nvarchar(MAX) = ' + CHAR(39) + ISNULL(@param1, 'NULL') + CHAR(39) + ';'
        SET @QRY = @QRY + ' DECLARE @param2 nvarchar(MAX) = ' + CHAR(39) + ISNULL(@param2, 'NULL') + CHAR(39) + ';'

Лично я все равно пропущу части параметров. Вы не избежите их, поэтому открыты для атак SQL-инъекций и / или неожиданных сбоев.

sp_executesql позволяет избежать этих проблем ...

        SET @QRY = @QRY + ' INSERT INTO tableName (column1, column2' 
        IF @param3 is not null
            SET @QRY = @QRY + ', column3'
        IF @param4 is not null
            SET @QRY = @QRY + ', column4'
        IF @param5 is not null
            SET @QRY = @QRY + ', column5'
        SET @QRY = @QRY + ') VALUES '
        SET @QRY = @QRY + ' (@param1, @param2 '
        IF @param3 is not null
            SET @QRY = @QRY + ', @param3'
        IF @param4 is not null
            SET @QRY = @QRY + ', @param4'
        IF @param5 is not null
            SET @QRY = @QRY + ', @param5'
        SET @QRY = @QRY + ')'

        EXEC sp_executesql
          @QRY,  
          N'@param1 NVARCHAR(MAX),
            @param2 NVARCHAR(MAX),
            @param3 NVARCHAR(MAX),
            @param4 NVARCHAR(MAX),
            @param5 NVARCHAR(MAX)',
          @param1,
          @param2,
          @param3,
          @param4,
          @param5

(Даже если вы передаете все 5 параметров, только те, которые вас интересуют, используются в INSERT, а передача их в качестве параметров предотвращает атаки SQL-инъекций или необходимость экранирования специальных символов и т. Д. и т. д. О, и вы никогда не рискуете объединить NULL.)

EDIT:

Я также оглянулся, чтобы посмотреть, есть ли лучший способ выбрать значения по умолчанию для столбцов. Хотя я не мог найти ничего «лучшего», есть «другой» подход ...

INSERT INTO
    tableName(
      column1,
      column2,
      column3,
      column4,
      column5
    )
SELECT
    @param1,
    @param2,
    ISNULL(@param3, MAX(CASE WHEN COLUMN_NAME = 'column3' THEN COLUMN_DEFAULT END)),
    ISNULL(@param4, MAX(CASE WHEN COLUMN_NAME = 'column4' THEN COLUMN_DEFAULT END)),
    ISNULL(@param5, MAX(CASE WHEN COLUMN_NAME = 'column5' THEN COLUMN_DEFAULT END))
FROM
    INFORMATION_SCHEMA.COLUMNS
WHERE
        TABLE_SCHEMA = 'dbo'        -- or whatever it really is in your case 
    AND TABLE_NAME   = 'tableName'
;

Для этого вообще не нужен динамический SQL. Но я не уверен, что это лучше, чем Dynamic SQL.

EDIT2:

ОН !!!

Ваш код показывает ROLLBACK в обработке ошибок, что означает, что в коде, который вы нам не показываете, BEGIN TRANSACTION?

У вас на самом деле есть COMMIT TRANSACTION где-нибудь ???

0 голосов
/ 26 марта 2019

Я лично получаю мурашки по коже, когда вижу такой сложный динамический код SQL для такого относительно простого варианта использования.

Кажется, вы просто хотите вставить от двух до пяти значений полей в новую запись в одной из ваших таблиц.

Мой самый важный вопрос здесь заключается в том, имеют ли эти три необязательных поля значения по умолчанию, отличные от NULL (с ​​помощью ограничения DEFAULT). Если это так, я бы также предоставил эти значения по умолчанию в качестве значений по умолчанию параметров вашей хранимой процедуры.

И я бы удалил значения по умолчанию из первых двух обязательных параметров.

И я бы изменил тело, чтобы использовать простой оператор вставки вместо всего этого сложного динамического SQL-компонента.

Примерно так:

ALTER PROCEDURE [dbo].[usp_Insert]
    @param1 NVARCHAR(MAX), 
    @param2 NVARCHAR(MAX), 
    @param3 NVARCHAR(MAX) = NULL, --optional
    @param4 NVARCHAR(MAX) = NULL, --optional
    @param5 NVARCHAR(MAX) = N'Some default value other than NULL'  --optional
AS
    SET NOCOUNT ON;
BEGIN
    IF NOT EXISTS (SELECT ...)
        BEGIN TRY
            INSERT INTO tableName (column1, column2, column3, column4, column5)
            VALUES (@param1, @param2, @param3, @param4, @param5);
        END TRY
        BEGIN CATCH
            IF (@@TRANCOUNT > 0) 
                BEGIN  
                    ROLLBACK TRANSACTION   
                    RAISERROR('Exception occurred. Transaction rolled back.', 16, 1);
                END;
        END CATCH;
    ELSE
        BEGIN  
            RAISERROR('Dulicate record found. Record not inserted.', 16, 1);
        END;
END;
GO

Но это только мое личное предпочтение ...

Дополнительное примечание:

Мне действительно нравится ваша структура SP, в которой вы устанавливаете любые операторы подготовки перед первым оператором BEGIN. Лично я всегда использую блок BEGIN ... END после AS, но вы можете добавить дополнительное разделение между подготовкой и выполнением. Ницца. ;)

0 голосов
/ 26 марта 2019

Я предполагаю, что вы делаете это, чтобы вставить значения по умолчанию для столбцов 3,4,5

Этот способ намного проще и предотвращает инъекцию

ALTER PROCEDURE [dbo].[usp_Insert]
    @param1 nvarchar(MAX) = NULL, 
    @param2 nvarchar(MAX) = NULL, 
    @param3 nvarchar(MAX) = NULL, --optional
    @param4 nvarchar(MAX) = NULL, --optional
    @param5 nvarchar(MAX) = NULL  --optional

AS

SET NOCOUNT ON;
DECLARE @QRY NVARCHAR(MAX) = '';

BEGIN
    IF NOT EXISTS (Select ...)
        BEGIN TRY

            SET @QRY = 'INSERT INTO TABLE_NAME (Col1, Col2, Col3, Col4, Col5) 
                VALUES (@Param1, @Param2, @Param3, @Param4, @Param5)'
            IF @Param3 IS NULL
                SET @QRY = REPLACE(@Qry, '@Param3', 'DEFAULT')
            IF @Param4 IS NULL
                SET @QRY = REPLACE(@Qry, '@Param4', 'DEFAULT')
            IF @Param5 IS NULL
                SET @QRY = REPLACE(@Qry, '@Param5', 'DEFAULT')
            sp_executesql @QRY, 
                 N'@param1 nvarchar(MAX), 
                 @param2 nvarchar(MAX),
                 @param3 nvarchar(MAX),
                 @param4 nvarchar(MAX),
                 @param5 nvarchar(MAX)',
                 @param1, @param2, @param3, @param4, @param5

        END TRY
        BEGIN CATCH
            IF (@@TRANCOUNT > 0) 
                BEGIN  
                    ROLLBACK TRANSACTION   
                    RAISERROR('Exception occurred. Transaction rolled back.',16,1)
                END
        END CATCH
    ELSE
        BEGIN  
            RAISERROR('Dulicate record found. Record not inserted.',16,1)
        END
END

если необязательные параметры равны нулю, ваш динамический sql будет выглядеть как

INSERT INTO TABLE_NAME (Col1, Col2, Col3, Col4, Col5) 
VALUES (@Param1, @Param2, DEFAULT, DEFAULT, DEFAULT)

Что является полностью действительным

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...