146

是否有一种优雅的方式来处理将 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)
4

5 回答 5

240

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

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

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

我真的不推荐阅读这篇文章来了解所有这些选项之间的权衡。

于 2008-09-04T13:32:34.507 回答
11

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

我发现的最佳解决方案是使用将文本拆分为单词的功能(此处发布了一些,或者您可以使用我博客中的这个),然后将其加入您的表格。就像是:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
于 2008-09-04T06:55:28.237 回答
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

它有你需要的所有(更多?)。

于 2008-09-04T07:05:13.957 回答
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

——凯文·费尔柴尔德

于 2008-09-04T16:36:51.897 回答
2

一个超快的 XML 方法,如果您想使用存储过程并传递逗号分隔的部门 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 的博客

于 2013-03-03T22:19:36.663 回答