接受多个Id值的T-SQL存储过程


145

是否有一种优美的方法来处理将ID列表作为参数传递给存储过程?

例如,我希望存储过程返回部门1、2、5、7、20。过去,我像下面的代码一样传入了以逗号分隔的ID列表,但这样做实在是太脏了。

我认为SQL Server 2005是我唯一适用的限制。

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)

是我刚刚发现的XML方法的一种变体。
JasonS

5
如果您使用的是SQL Server 2008,则可以使用表值参数。http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
伊恩·尼尔森

Answers:


237

在过去的16年中,Erland Sommarskog一直对这个问题保持权威性的答案:SQL Server中的数组和列表

至少有十二种方法可以将数组或列表传递给查询。每个人都有自己独特的优点和缺点。

  • 表值参数。仅限于SQL Server 2008和更高版本,并且可能是最接近通用的“最佳”方法。
  • 迭代法。传递定界字符串并循环通过它。
  • 使用CLR。仅.NET语言的SQL Server 2005及更高版本。
  • XML。非常适合插入许多行;可能对SELECT来说太过分了。
  • 数字表。比简单的迭代方法更高的性能/复杂度。
  • 定长元素。固定长度提高了定界字符串的速度
  • 数字函数。数字表和固定长度的变体,其中数字是在函数中生成的,而不是从表中获取的。
  • 递归公用表表达式(CTE)。SQL Server 2005及更高版本,仍然没有比迭代方法复杂和更高的性能。
  • 动态SQL。可能很慢,并且具有安全隐患。
  • 参数传递给列表。乏味且容易出错,但是很简单。
  • 真的很慢的方法。使用charindex,patindex或LIKE的方法。

我确实不推荐阅读本文以了解所有这些选项之间的权衡。


11

是的,您当前的解决方案很容易受到SQL注入攻击。

我找到的最好的解决方案是使用一个将文本拆分为单词的函数(此处发布了一些内容,或者您​​可以从我的Blog中使用此功能),然后将其连接到表中。就像是:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId

14
除非可以直接从不受信任的客户端调用存储的proc,否则我不确定它是否“容易受到SQL注入攻击”,在这种情况下,您会遇到更大的问题。服务层代码应从强类型数据(例如int [] departmentIds)生成@DepartmentIds字符串,在这种情况下,您会没事的。
安东尼

很棒的解决方案,@ Matt Hamilton。不知道这是否会对任何人有帮助,但是当我使用“ join dbo.SplitWords(@MyParameterArray)p ON CHARINDEX(p.value,d.MyFieldToSearch)> 0”搜索文本字段时,在SQL Server 2008r上我得到了更准确的结果
Darkloki

3

您可能要考虑的一种方法是,如果要大量使用这些值,请先将它们写入临时表。然后,您只需像平常一样加入即可。

这样,您只需解析一次。

使用其中一种“拆分” UDF最简单,但是有很多人发布了这些示例,我认为我会走另外一条路;)

本示例将创建一个临时表供您加入(#tmpDept),并使用您传入的部门ID填充它。我假设您要用逗号分隔它们,但是您当然可以更改它可以随你想要。

IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
    DROP TABLE #tmpDept
END

SET @DepartmentIDs=REPLACE(@DepartmentIDs,' ','')

CREATE TABLE #tmpDept (DeptID INT)
DECLARE @DeptID INT
IF IsNumeric(@DepartmentIDs)=1
BEGIN
    SET @DeptID=@DepartmentIDs
    INSERT INTO #tmpDept (DeptID) SELECT @DeptID
END
ELSE
BEGIN
        WHILE CHARINDEX(',',@DepartmentIDs)>0
        BEGIN
            SET @DeptID=LEFT(@DepartmentIDs,CHARINDEX(',',@DepartmentIDs)-1)
            SET @DepartmentIDs=RIGHT(@DepartmentIDs,LEN(@DepartmentIDs)-CHARINDEX(',',@DepartmentIDs))
            INSERT INTO #tmpDept (DeptID) SELECT @DeptID
        END
END

这将允许您传入一个部门ID,在多个ID之间使用逗号分隔,甚至在多个ID之间使用逗号和空格传递。

因此,如果您执行以下操作:

SELECT Dept.Name 
FROM Departments 
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name

您会看到传入的所有部门ID的名称...

同样,可以使用函数填充临时表来简化此操作...我主要是在没有一个函数的情况下这样做的,只是为了消除一些无聊的事情:-P

-凯文·费尔柴尔德(Kevin Fairchild)


3

您可以使用XML。

例如

declare @xmlstring as  varchar(100) 
set @xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>' 

declare @docid int 

exec sp_xml_preparedocument @docid output, @xmlstring

select  [id],parentid,nodetype,localname,[text]
from    openxml(@docid, '/args', 1) 

内置命令sp_xml_preparedocument

这将产生输出:

id  parentid    nodetype    localname   text
0   NULL        1           args        NULL
2   0           1           arg         NULL
3   2           2           value       NULL
5   3           3           #text       42
4   0           1           arg2        NULL
6   4           3           #text       -1

可以满足您所有(更多?)的需求。


2

超高速XML方法,如果您想使用存储过程并传递以逗号分隔的Department ID列表:

Declare @XMLList xml
SET @XMLList=cast('<i>'+replace(@DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))

所有功劳归于Guru Brad Schulz的博客


-3

试试这个:

@list_of_params varchar(20) -- value 1, 2, 5, 7, 20 

SELECT d.[Name]
FROM Department d
where @list_of_params like ('%'+ CONVERT(VARCHAR(10),d.Id)  +'%')

很简单。


1
非常简单-而且非常错误。但是,即使您要在代码中解决该问题,也将非常缓慢。有关详细信息,请参见接受的答案中的“真正慢的方法”链接。
塞巴斯蒂安·梅因
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.