如何将包含列表的变量传递给动态SQL查询?

时间:2019-01-12 12:08:47

标签: sql sql-server tsql

我有一个函数,可以将数字的字符串列表转换为整数表:

USE [IFRS_Temp]
GO
/****** Object:  UserDefinedFunction [dbo].[CSVToTable]    Script Date: 01/12/2019 3:36:10 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER FUNCTION [dbo].[CSVToTable] (@InStr VARCHAR(MAX))
RETURNS @TempTab TABLE
   (id int not null)
AS
BEGIN
    ;-- Ensure input ends with comma
    SET @InStr = REPLACE(@InStr + ',', ',,', ',')
    DECLARE @SP INT
DECLARE @VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', @INSTR ) <> 0 
BEGIN
   SELECT  @SP = PATINDEX('%,%',@INSTR)
   SELECT  @VALUE = LEFT(@INSTR , @SP - 1)
   SELECT  @INSTR = STUFF(@INSTR, 1, @SP, '')
   INSERT INTO @TempTab(id) VALUES (@VALUE)
END
    RETURN
END

declare @listOfIDs varchar(1000);
SET @listOfIDs = '5, 6, 7, 8, 9, 15, 28, 31, 49, 51, 59, 61'; 

select id from  [dbo].[CSVToTable] (@listOfIDs) --this code is ok5

结果正确:

6
7
8
9
15
28
31
49
51
59
61

这会引发错误:

exec('select id from  [dbo].[CSVToTable] ('+@listOfIDs+')') -- error

结果:

  

过程或函数dbo.CSVToTable指定的参数过多。

我需要第二个查询,因为我的查询是动态的。 谢谢

2 个答案:

答案 0 :(得分:6)

简单

EXECUTE ('select id from  [dbo].[CSVToTable] ('''+@listOfIDs+''')')
        declare @listOfIDs varchar(1000);

或者,这是更好的方法

SET @listOfIDs = '5, 6, 7, 8, 9, 15, 28, 31, 49, 51, 59, 61'; 

EXECUTE sp_executesql N'select id from  [dbo].[CSVToTable] (@listOfIDs)',
                      N'@listOfIDs VARCHAR(1000)',
                      @listOfIDs;
  • 为什么会出现此错误?
      

    过程或函数dbo.CSVToTable指定的参数过多。

因为您确实传递了太多参数,然后需要传递更多的参数,才能理解此运行此查询并查看真正传递给函数的信息

SELECT 'select id from  [dbo].[CSVToTable] ('+@listOfIDs+')';

这将返回(这是您真正要执行的操作)

select id from  [dbo].[CSVToTable] (5, 6, 7, 8, 9, 15, 28, 31, 49, 51, 59, 61)

代替(这就是您所需要的)

SELECT 'select id from  [dbo].[CSVToTable] ('''+@listOfIDs+''')';

  • 好,但是为什么sp_executesqlexec好?

简而言之,EXEC将迫使您将所有变量连接到一个字符串中,这是最糟糕的事情,并且使您的代码完全可以接受 SQL注入。请参见 Bad Habits to Kick : Using EXEC() instead of sp_executesql ,这并不意味着sp_executesql是100%安全的,但允许在{{1}时对语句进行参数化 }不会,因此就SQL注入而言,它比EXEC()更加安全。


最后,由于您标记了并且未指定版本,因此建议您使用 SPLIT_STRING() 函数(2016+)而不是您使用的函数,如果您没有2016+版本,而没有使用EXEC循环创建自己的版本以获得更好的性能,则导致WHILE循环执行速度较慢,因此应避免使用它。

示例:

答案 1 :(得分:0)

您的SQL存在一些问题,但让我们从以下内容开始:

exec('select id from  [dbo].[CSVToTable] ('+@listOfIDs+')') -- error

失败的原因是因为它将SQL转换为:

select id from  [dbo].[CSVToTable] (1,2,3,4)

您可以很快看到为什么这是一个问题。而是将值作为参数传递:

EXEC sp_executesql N'SELECT id FROM [dbo].[CSVToTable] (@IDs);', N'@IDs varchar(1000)', @IDs = @listOfIDs;

这将使您的代码免于注入,但是,您的函数CSVToTable使用WHILE意味着这将导致性能下降。如果您使用的是SQL Server 2016+,则可以访问SPLIT_SPLIT。它会比迭代过程快得多。

如果没有,我建议看看SQL Server 2008的DelimitedSplit8K或SQL Server 2012和2014的DelimitedSplit8k_lead。您会发现它们在性能上要好得多。