我试图动态创建触发器,但遇到了一个困惑的问题周围使用sp_executesql
和传递参数到动态SQL。下面简单的测试案例的工作原理:
DECLARE @tableName sysname = 'MyTable';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
PRINT 1
END';
EXEC sp_executesql @sql
不过,我希望能够用@tableName
(等价值)为脚本中的变量,所以我通过它一起给sp_executesql
电话:
DECLARE @tableName sysname = 'ContentItems';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
PRINT @tableName
END';
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName
当运行上面,我得到一个错误:
消息156,级别15,状态1,2号线 关键字“触发”附近的语法不正确。
想我几件事情之后,我发现,即使我没有在动态SQL使用@tableName
可言,我仍然得到这个错误。我也收到此错误试图创建一个PROCEDURE
(除,显然,该消息是关键字“过程”附近有语法错误。)
由于SQL运行良好直接或时不提供参数sp_executesql
,这似乎是我遇到的SQL引擎真正的限制,但我没有看到它的任何地方记录。有谁知道,如果有一种方法可以接受一个动态CREATE
脚本,或者至少有洞察力交代所遇到的潜在限制吗?
更新我可以添加一个PRINT
声明,并得到下面的SQL,这是有效的,并成功运行(时直接运行)。我仍然得到错误,如果没有什么动态的SQL(它只是一个字符串,没有连接)。
CREATE TRIGGER TR_ContentItems ON ContentItems FOR INSERT
AS
BEGIN
PRINT @tableName
END
我也得到了同样的错误是否使用sysname
或nvarchar(max)
的参数。
如果您执行create trigger
声明,你说你印......你会发现,这是行不通的。在触发体内的print语句正试图输出@tablename
,但从来没有定义,所以你会得到一个错误:
必须声明标量变量“@tablename”。
但是,这是不是你的主要问题。至于为什么你似乎无法执行与参数execute_sql
DDL语句,我无法找到任何文件,以解释为什么...但你的经验和其他人证明了它的麻烦。我相信这个职位有着相当不错的理论:sp_executesql adds statements to executed dynamic script?
但是,您可以使用EXECUTE
语句DDL语句执行动态SQL。所以你可以做的是创建一个验证你的表名,然后创建一个动态的SQL字符串与sp_executesql
语句来执行参数EXECUTE
声明。
它并不漂亮,但它的工作原理:
DECLARE @tableName sysname = 'MyTable';
DECLARE @sql nvarchar(max) =
N'
set @tableName = (SELECT name FROM sys.tables WHERE OBJECT_ID = OBJECT_ID(@tableName)) --validate table
DECLARE @CreateTriggerSQL as varchar(max) =
''
CREATE TRIGGER '' + QUOTENAME(''TR_'' + @tableName) + '' ON '' + QUOTENAME( @tableName) + '' FOR INSERT
AS
BEGIN
PRINT '''''' + @tableName + ''''''
END
''
print isnull(@CreateTriggerSQL, ''INVALID TABLE'')
exec (@CreateTriggerSQL)
';
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName;
你也可以转换到这个存储过程带有参数,而不是运行sp_executesql
如果这是更方便。它看起来有点清洁:
CREATE PROCEDURE sp_AddTriggerToTable (@TableName AS sysname) AS
set @tableName = (SELECT name FROM sys.tables WHERE OBJECT_ID = OBJECT_ID(@tableName)) --validate table
DECLARE @CreateTriggerSQL as varchar(max) =
'
CREATE TRIGGER ' + QUOTENAME('TR_' + @tableName) + ' ON ' + QUOTENAME( @tableName) + ' FOR INSERT
AS
BEGIN
PRINT ''' + @tableName + '''
END
'
print isnull(@CreateTriggerSQL, 'INVALID TABLE')
exec (@CreateTriggerSQL)
GO
我强烈告诫不要使用动态SQL和表名。你是在和自己的一些严重的SQL注入的问题。您应该验证任何进入@tableName
变量。
这就是说,在你的榜样?
DECLARE @tableName sysname = 'ContentItems';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
PRINT @tableName
END';
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName
...您要输入你的声明@tableName
为你创造@sql
的文本,然后你想通过spexecutesql
传递参数。这使你的@sql
无效试图调用它的时候。
你可以试试:
DECLARE @tableName sysname = 'ContentItems';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_'' + @tableName + N'' ON '' + @tableName + N'' FOR INSERT
AS
BEGIN
PRINT @tableName
END';
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName
...它会给你的串...
'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
PRINT @tableName
END'
......然后可以接受你通过参数...
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName ;
同样,我递东西到动态SQL将使用动态表名前使用一些重验证(和白名单)。
注:如下所述,我相信你是有限的,可与sp_executesql()
执行DML语句,我觉得参数也是有限的。并根据您的其他意见,这听起来并不像你真的需要一个动态的过程,但一种方式重复一个特定的任务元素屈指可数。如果是这样的话,我的建议是与副本做手工/粘贴,然后执行该语句。
由于SQL运行良好直接或时不提供参数,sp_executesql的,这似乎是我遇到的SQL引擎真正的限制,但我没有看到它的任何地方记录。
这种行为被记录在案,虽然并不直观。从the documentation下的触发限制主题相关的摘录:
CREATE TRIGGER必须是批处理的第一个语句
当你执行一个参数化查询,参数声明都算作是批的一部分。因此,CREATE TRIGGER
批次(和其他的CREATE等特效,函数等可编程对象的语句)不能被作为一个参数化的查询执行。
当您尝试作为一个参数化查询运行CREATE TRIGGER
你得到无效的语法错误消息不是特别有帮助。下面是一个使用无证和不支持的内部参数化查询语法代码的简化版本。
EXECUTE(N'(@tableName sysname = N''MyTable'')CREATE TRIGGER TR_MyTable ON dbo.MyTable FOR INSERT AS');
这至少会产生错误唱出CREATE TRIGGER
限制:
消息1050,级别15,状态1,行73这个语法只允许参数化查询。消息111,级别15,状态1,行73“CREATE TRIGGER”必须是查询批次中的第一个语句。
同样执行这种方法的另一个参数化的语句成功运行:
EXECUTE (N'(@tableName sysname = N''MyTable'')PRINT @tableName');
但是,如果你不实际使用的参数在批处理,将导致错误
EXECUTE (N'(@tableName sysname = N''MyTable'')PRINT ''done''');
消息1050,15级,状态1,行75这个语法只允许参数化查询。
底线是,你需要建立CREATE TRIGGER
语句不带参数的字符串,并执行该语句作为一种非参数化查询创建触发器。
是否有可能发出create使用sp_executesql与参数的语句?
简单的答案是“否”,你不能
根据MSDN
一般而言,参数仅在数据操作语言(DML)语句有效,而不是在数据定义语言(DDL)语句
您可以检查这个Statement Parameters更多细节
问题是什么?
参数只能在地方标文字,如带引号的字符串或日期,或数值。你不能参数化一个DDL
操作。
可以做些什么?
我相信,你要使用参数化sp_executesql
是避免任何SQL注入攻击。要为DDL
操作,你可以做以下的事情,以尽量减少攻击的可能性实现这一目标。
QUOTENAME()
为SYSNAME
参数,如触发名,表名和列名。DDL
用户帐户,应该只有有限的权限。像只CREATE
允许一个特定的模式。DROP
特定关键字。任何解决方法吗?
如果你想使用sp_executesql
参数化的语句,在这种情况下,你可以得到一个OUTPUT
变量要执行的查询,并运行在下一语句的查询类似以下。
通过这种方式,来sp_executesql
第一通话将参数查询,而实际执行将被第二次调用执行,以sp_executesql
例如。
DECLARE @TableName VARCHAR(100) = 'MyTable'
DECLARE @returnStatement NVARCHAR(max);
DECLARE @sql1 NVARCHAR(max)=
N'SELECT @returnStatement = ''CREATE TRIGGER TR_''
+ @TableName + '' ON '' + @TableName + '' FOR INSERT AS BEGIN PRINT 1 END'''
EXEC Sp_executesql
@sql1,
N'@returnStatement VARCHAR(MAX) OUTPUT, @TableName VARCHAR(100)',
@returnStatement output,
@TableName
EXEC Sp_executesql @returnStatement
是否有可能发出create使用sp_executesql与参数的语句?
答案是“是”,但小的调整:
USE msdb
DECLARE @tableName sysname = 'sysjobsteps';
DECLARE @sql nvarchar(max) = N'
EXECUTE ('' -- Added nested EXECUTE()
CREATE TRIGGER [TR_'' + @tableName + N''] ON ['' + @tableName + N''] FOR INSERT
AS
BEGIN
PRINT '''''+@tableName+'''''
END''
)' -- End of EXECUTE()
EXEC sp_executesql @sql, N'@tableName sysname', @tableName=@tableName
调整名单:
我在寻找特定的(理想情况下,记录)与参数和sp_executesql的限制,如果有,这些具体的限制,任何变通办法(除了不使用参数)
在这种情况下,DDL命令的限制,而不是对sp_executesql。 DDL语句不能使用变量的参数进行调整。微软文档中说:
变量只能在表达式中使用,不到位对象名称或关键字。构建动态SQL语句,使用EXECUTE。
因此,执行该解决方案是由我提供的解决方法
我个人讨厌触发器和尽量避免他们的大部分时间;)
但是,如果你真的,真的需要这个东西的动态,你应该使用sp_MSforeachtable,避免注射不惜任何代价(如肖恩指出):
EXEC sys.sp_MSforeachtable
@command1 = '
DECLARE @sql NVARCHAR(MAX)
SET @sql = CONCAT(''CREATE TRIGGER TR_''
, REPLACE(REPLACE(REPLACE(''?'', ''[dbo].'', ''''),''['',''''),'']'','''')
, '' ON ? FOR INSERT
AS
BEGIN
PRINT ''''?'''';
END;'');
EXEC sp_executesql @sql;'
, @whereand = ' AND object_id IN (SELECT object_id FROM sys.objects
WHERE name LIKE ''%ContentItems%'')';
如果你想使用该参数为字符串,之前和参数名后加上双“
像这样 :
DECLARE @tableName sysname = 'ContentItems';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
print ''' + @tableName
+''' END';
EXEC sp_executesql @sql
如果你想使用它作为表名,用select代替打印,
像这样 :
DECLARE @tableName sysname = 'ContentItems';
DECLARE @sql nvarchar(max) = N'
CREATE TRIGGER TR_' + @tableName + N' ON ' + @tableName + N' FOR INSERT
AS
BEGIN
select * from ' + @tableName
+' END';
EXEC sp_executesql @sql