接受多个Id值的T-SQL存储过程
时间:2020-03-05 18:47:28 来源:igfitidea点击:
是否有一种优美的方法来处理将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)
解决方案
回答
是的,我们当前的解决方案很容易受到SQL注入攻击。
我找到的最好的解决方案是使用一种将文本拆分为单词的功能(此处有一些张贴内容,或者我们可以从我的博客中使用此功能),然后将其加入表中。就像是:
SELECT d.[Name] FROM Department d JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
回答
我们可以使用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
哪些具有我们所需的全部(更多?)。
回答
在过去的16年中,Erland Sommarskog始终保持对这个问题的权威答案:SQL Server中的数组和列表。
至少有十二种方法可以将数组或者列表传递给查询。每个人都有自己独特的优点和缺点。
- 表值参数。仅限于SQL Server 2008和更高版本,并且可能是最接近通用的"最佳"方法。
- 迭代方法。传递一个定界字符串并循环通过它。
- 使用CLR。仅.NET语言的SQL Server 2005及更高版本。
- XML。非常适合插入许多行;可能对SELECT来说是过大的杀伤力。
- 数字表。比简单的迭代方法更高的性能/复杂度。
- 定长元素。固定长度提高了定界字符串的速度
- 数字的功能。数字表和固定长度的变体,其中数字是在函数中生成的,而不是从表中获取的。
- 递归公用表表达式(CTE)。 SQL Server 2005及更高版本,仍然没有比迭代方法复杂和更高的性能。
- 动态SQL。可能很慢,并且具有安全隐患。
- 将列表作为许多参数传递。乏味且容易出错,但简单。
- 真的很慢的方法。使用charindex,patindex或者LIKE的方法。
我真的不建议阅读本文以了解所有这些选项之间的权衡。
回答
我们可能要考虑的一种方法是,首先将它们写入临时表。然后,我们只需像平常一样加入即可。
这样,我们只需解析一次。
使用其中一种"拆分" 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)