我正在尝试编写一个存储过程,该过程将插入记录,其中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
如果有更好的方法来实现我的目标,请给我启发。谢谢!
编辑: 抱歉,我很困惑。是的,我的代码中有一个开始并提交。同样,第3、4和5列的列名也作为参数传递给存储过程。这就是为什么我选择使用动态查询的原因。我相应地编辑了上面的代码。很抱歉忽略了非常重要的部分!
答案 0 :(得分:1)
我可以看到的一个问题(感谢@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 Injection攻击和/或意外失败的影响。
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 Injection攻击或转义特殊字符的需要等等,等等。哦,您再也没有串联NULL
的风险。)
编辑:
我也环顾四周,看看是否有更好的方法来获取列默认值。尽管我找不到“更好”的东西,但是有一种“不同”的方法...
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。但是我不认为它比动态SQL更好。
EDIT2:
OH !!!
您的代码在错误处理中显示ROLLBACK
,这意味着您没有向我们显示的代码中有BEGIN TRANSACTION
吗?
您实际上在任何地方都有COMMIT TRANSACTION
吗?
答案 1 :(得分:1)
您必须检查参数是否为NULL,请看下面的示例
declare @param1 nvarchar(10) = null
declare @QRY nvarchar(100)
set @QRY = 'test ' + @param1
select @QRY
结果将为NULL
,因为其中一个隐含值为空
因此,您应该检查是否存在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) + ';
答案 2 :(得分:1)
使用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...
是本地存储的proc参数。 @p1
... @px
是动态SQL上下文参数。确定可以传递未使用的额外参数(NULL时为@p3
。
您的BEGIN TRY ... CATCH事务处理不正确。有关正确的模式,请参见http://rusanu.com/2009/06/11/exception-handling-and-nested-transactions/。
答案 3 :(得分:0)
我假设您正在执行此操作,以插入第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
如果可选参数为null,则动态sql看起来像
INSERT INTO TABLE_NAME (Col1, Col2, Col3, Col4, Col5)
VALUES (@Param1, @Param2, DEFAULT, DEFAULT, DEFAULT)
哪个完全有效
答案 4 :(得分:0)
当我针对如此相对简单的用例看到如此复杂的动态SQL代码时,我个人会感到鸡皮。
似乎您只想在其中一个表的新记录中插入2到5个字段值。
我最重要的问题是,这三个可选字段是否具有除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语句之前设置任何准备语句。我个人总是在AS之后使用BEGIN ... END块,但是您可以在准备和执行之间添加额外的分隔。真好;)