是否有可能发出create使用sp_executesql与参数的语句?

问题描述 投票:13回答:7

我试图动态创建触发器,但遇到了一个困惑的问题周围使用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

我也得到了同样的错误是否使用sysnamenvarchar(max)的参数。

sql-server dynamic-sql sp-executesql
7个回答
4
投票

如果您执行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

2
投票

我强烈告诫不要使用动态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语句,我觉得参数也是有限的。并根据您的其他意见,这听起来并不像你真的需要一个动态的过程,但一种方式重复一个特定的任务元素屈指可数。如果是这样的话,我的建议是与副本做手工/粘贴,然后执行该语句。


2
投票

由于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语句不带参数的字符串,并执行该语句作为一种非参数化查询创建触发器。


2
投票

是否有可能发出create使用sp_executesql与参数的语句?

简单的答案是“否”,你不能

根据MSDN

一般而言,参数仅在数据操作语言(DML)语句有效,而不是在数据定义语言(DDL)语句

您可以检查这个Statement Parameters更多细节

问题是什么?

参数只能在地方标文字,如带引号的字符串或日期,或数值。你不能参数化一个DDL操作。

可以做些什么?

我相信,你要使用参数化sp_executesql是避免任何SQL注入攻击。要为DDL操作,你可以做以下的事情,以尽量减少攻击的可能性实现这一目标。

  1. 使用分隔符:可以使用QUOTENAME()SYSNAME参数,如触发名,表名和列名。
  2. 限制权限:您正在使用运行动态DDL用户帐户,应该只有有限的权限。像只CREATE允许一个特定的模式。
  3. 隐藏错误信息:不要把实际的错误给用户。 SQL注入主要是由试错法进行。如果隐藏了实际的错误信息,这将成为很难破解它。
  4. 输入验证:你总是可以有这验证输入字符串的函数,逃避所需的字符,选中喜欢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 

1
投票

是否有可能发出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

调整名单:

  1. 额外EXECUTE参与,评论如下解释了为什么
  2. 额外的方括号的加入使SQL注入稍硬

我在寻找特定的(理想情况下,记录)与参数和sp_executesql的限制,如果有,这些具体的限制,任何变通办法(除了不使用参数)

在这种情况下,DDL命令的限制,而不是对sp_executesql。 DDL语句不能使用变量的参数进行调整。微软文档中说:

变量只能在表达式中使用,不到位对象名称或关键字。构建动态SQL语句,使用EXECUTE。

来源:DECLARE (Transact-SQL)

因此,执行该解决方案是由我提供的解决方法


0
投票

我个人讨厌触发器和尽量避免他们的大部分时间;)

但是,如果你真的,真的需要这个东西的动态,你应该使用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%'')';

0
投票

如果你想使用该参数为字符串,之前和参数名后加上双“

像这样 :

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
© www.soinside.com 2019 - 2024. All rights reserved.