如何将数组传递到 SQL Server 存储过程
Posted
技术标签:
【中文标题】如何将数组传递到 SQL Server 存储过程【英文标题】:How to pass an array into a SQL Server stored procedure 【发布时间】:2012-06-21 13:47:14 【问题描述】:如何将数组传递给 SQL Server 存储过程?
例如,我有一个员工列表。我想将此列表用作表格并将其与另一个表格连接。但是员工列表应该作为 C# 的参数传递。
【问题讨论】:
先生希望此链接对您有所帮助Passing a list/array to SQL Server SP 和Parameterize an SQL IN clause同一个类 【参考方案1】:SQL Server 2008(或更新版本)
首先,在您的数据库中,创建以下两个对象:
CREATE TYPE dbo.IDList
AS TABLE
(
ID INT
);
GO
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List AS dbo.IDList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT ID FROM @List;
END
GO
现在在您的 C# 代码中:
// Obtain your list of ids to send, this is just an example call to a helper utility function
int[] employeeIds = GetEmployeeIds();
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID", typeof(int)));
// populate DataTable from your List here
foreach(var id in employeeIds)
tvp.Rows.Add(id);
using (conn)
SqlCommand cmd = new SqlCommand("dbo.DoSomethingWithEmployees", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("@List", tvp);
// these next lines are important to map the C# DataTable object to the correct SQL User Defined Type
tvparam.SqlDbType = SqlDbType.Structured;
tvparam.TypeName = "dbo.IDList";
// execute query, consume results, etc. here
SQL Server 2005
如果您使用的是 SQL Server 2005,我仍然建议使用 XML 上的拆分函数。首先,创建一个函数:
CREATE FUNCTION dbo.SplitInts
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM
( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
GO
现在您的存储过程可以是:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ',');
END
GO
在您的 C# 代码中,您只需将列表传递为 '1,2,3,12'
...
我发现传递表值参数的方法简化了使用它的解决方案的可维护性,并且与其他实现(包括 XML 和字符串拆分)相比,通常具有更高的性能。
输入是明确定义的(没有人必须猜测分隔符是逗号还是分号),并且我们不依赖于其他处理函数,这些函数在不检查存储过程的代码的情况下并不明显。
与涉及用户定义的 XML 模式而不是 UDT 的解决方案相比,这涉及相似数量的步骤,但根据我的经验,管理、维护和阅读的代码要简单得多。
在许多解决方案中,您可能只需要这些 UDT(用户定义的类型)中的一个或几个,这些 UDT(用户定义的类型)可以重复用于许多存储过程。与这个例子一样,常见的要求是传递一个 ID 指针列表,函数名称描述了这些 Id 应该代表什么上下文,类型名称应该是通用的。
【讨论】:
我喜欢表格参数的想法——从来没有想过——干杯。值得将分隔符传递给 SplitInts() 函数调用。 如果只能访问逗号分隔的字符串,如何使用 table 参数 @bdwain 会破坏目的 - 您必须使用拆分功能将其分成几行以放入 TVP。在您的应用程序代码中分解它。 @AaronBertrand 感谢您的回复,我实际上只是想通了。我必须在括号中使用子选择:SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
。
@th1rdey3 它们是隐式可选的。 ***.com/a/18926590/61305【参考方案2】:
根据我的经验,通过从employeeIDs 创建一个分隔表达式,这个问题有一个棘手而好的解决方案。您应该只创建一个类似';123;434;365;'
的字符串表达式,其中123
、434
和365
是一些员工ID。通过调用以下过程并将此表达式传递给它,您可以获取所需的记录。您可以轻松地将“另一个表”加入此查询。此解决方案适用于所有版本的 SQL Server。此外,与使用表变量或临时表相比,它是非常快速和优化的解决方案。
CREATE PROCEDURE dbo.DoSomethingOnSomeEmployees @List AS varchar(max)
AS
BEGIN
SELECT EmployeeID
FROM EmployeesTable
-- inner join AnotherTable on ...
where @List like '%;'+cast(employeeID as varchar(20))+';%'
END
GO
【讨论】:
不错!我真的很喜欢这种在 int 键上进行过滤的方法! +1 @MDV2000 谢谢 :) 在字符串键上,这也有很好的性能,因为没有将 int 列转换为 varchar ... 我来晚了,但这很聪明!非常适合我的问题。 这太棒了!我一定会用这个,谢谢 这不是一个好的解决方案,因为 Like % % 会导致对表进行扫描【参考方案3】:为您的存储过程使用表值参数。
当您从 C# 传入时,您将添加数据类型为 SqlDb.Structured 的参数。
请看这里:http://msdn.microsoft.com/en-us/library/bb675163.aspx
例子:
// Assumes connection is an open SqlConnection object.
using (connection)
// Create a DataTable with the modified rows.
DataTable addedCategories =
CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand(
"usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue(
"@t***ewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
【讨论】:
【参考方案4】:您需要将其作为 XML 参数传递。
编辑:我的项目中的快速代码给你一个想法:
CREATE PROCEDURE [dbo].[GetArrivalsReport]
@DateTimeFrom AS DATETIME,
@DateTimeTo AS DATETIME,
@HostIds AS XML(xsdArrayOfULong)
AS
BEGIN
DECLARE @hosts TABLE (HostId BIGINT)
INSERT INTO @hosts
SELECT arrayOfUlong.HostId.value('.','bigint') data
FROM @HostIds.nodes('/arrayOfUlong/u') as arrayOfUlong(HostId)
然后您可以使用临时表加入您的表。 我们将 arrayOfUlong 定义为内置 XML 模式以维护数据完整性,但您不必这样做。我建议使用它,因此这里有一个快速代码,可确保您始终获得带有 long 的 XML。
IF NOT EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = 'xsdArrayOfULong')
BEGIN
CREATE XML SCHEMA COLLECTION [dbo].[xsdArrayOfULong]
AS N'<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="arrayOfUlong">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded"
name="u"
type="xs:unsignedLong" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>';
END
GO
【讨论】:
我认为当有很多行时使用表变量是个坏主意?使用临时 (#table) 表不是更好的性能吗? @ganders:反之亦然。【参考方案5】:上下文总是很重要的,比如数组的size和complexity。对于中小型列表,这里发布的几个答案都很好,但应该做出一些澄清:
对于拆分分隔列表,基于 SQLCLR 的拆分器是最快的。如果你想自己写,有很多例子,或者你可以下载免费的SQL# CLR 函数库(我写的,但是 String_Split 函数和许多其他函数是完全免费的)。 拆分基于 XML 的数组 可以很快,但您需要使用基于属性的 XML,而不是基于元素的 XML(这是此处答案中显示的唯一类型,尽管 @AaronBertrand 的 XML示例是最好的,因为他的代码使用了text()
XML 函数。有关使用 XML 拆分列表的更多信息(即性能分析),请查看 Phil Factor 的"Using XML to pass lists as parameters in SQL Server"。
使用 TVP 非常棒(假设您至少使用 SQL Server 2008 或更高版本),因为数据会流式传输到 proc 并显示为预解析和强类型的表变量。然而,在大多数情况下,将所有数据存储在DataTable
中意味着复制内存中的数据,因为它是从原始集合中复制的。因此,使用DataTable
传入 TVP 的方法不适用于较大的数据集(即不能很好地扩展)。
与简单的 Int 或 String 分隔列表不同,XML 可以处理多个一维数组,就像 TVP 一样。但也与DataTable
TVP 方法一样,XML 不能很好地扩展,因为它需要将内存中的数据大小增加一倍以上,因为它需要额外考虑 XML 文档的开销。
综上所述,如果您使用的数据很大或不是很大但一直在增长,那么IEnumerable
TVP 方法是最佳选择,因为它将数据流式传输到 SQL Server(如@ 987654329@ 方法),但不需要在内存中复制任何集合(与任何其他方法不同)。我在这个答案中发布了一个 SQL 和 C# 代码示例:
Pass Dictionary to Stored Procedure T-SQL
【讨论】:
【参考方案6】:正如上面其他人所指出的,一种方法是将数组转换为字符串,然后在 SQL Server 中拆分字符串。
从 SQL Server 2016 开始,有一种内置的方法来拆分字符串,称为
STRING_SPLIT()
它返回一组可以插入到临时表(或真实表)中的行。
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')
会产生:
价值 ----- 123 456 789 246 22 33 44 55 66如果你想变得更漂亮:
DECLARE @tt TABLE (
thenumber int
)
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
INSERT INTO @tt
SELECT value FROM STRING_SPLIT(@str, ';')
SELECT * FROM @tt
ORDER BY thenumber
会给你与上面相同的结果(除了列名是“thenumber”),但已排序。您可以像使用任何其他表一样使用表变量,因此您可以根据需要轻松地将其与数据库中的其他表连接起来。
请注意,您的 SQL Server 安装必须在兼容级别 130 或更高级别才能识别 STRING_SPLIT()
函数。您可以使用以下查询检查您的兼容性级别:
SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';
大多数语言(包括 C#)都有一个“join”函数,可用于从数组创建字符串。
int[] myarray = 22, 33, 44;
string sqlparam = string.Join(";", myarray);
然后将sqlparam
作为参数传递给上面的存储过程。
【讨论】:
【参考方案7】:sql server 不支持数组,但是有几种方法可以将集合传递给存储过程。
-
通过使用数据表
通过使用 XML。尝试将您的集合转换为 xml 格式,然后将其作为输入传递给存储过程
以下链接可能对您有所帮助
passing collection to a stored procedure
【讨论】:
【参考方案8】:这将对您有所帮助。 :) 按照接下来的步骤,
打开查询编辑器
复制粘贴下面的代码,它将创建将String转换为Int的函数
CREATE FUNCTION dbo.SplitInts
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM
( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
GO
创建以下存储过程
CREATE PROCEDURE dbo.sp_DeleteMultipleId
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM TableName WHERE Id IN( SELECT Id = Item FROM dbo.SplitInts(@List, ','));
END
GO
使用exec sp_DeleteId '1,2,3,12'
执行此SP,这是您要删除的一串Id,
您可以在 C# 中将数组转换为字符串,并将其作为存储过程参数传递,如下所示,
int[] intarray = 1, 2, 3, 4, 5 ;
string[] result = intarray.Select(x=>x.ToString()).ToArray();
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandText = "sp_DeleteMultipleId";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Id",SqlDbType.VARCHAR).Value=result ;
这将删除单个存储过程调用中的多行。一切顺利。
【讨论】:
我已经使用了这个逗号分隔的解析功能,它适用于小数据集,如果你检查它的执行计划,它会导致大数据集出现问题,你必须有多个 csv 列表在存储过程中【参考方案9】:我一直在搜索有关如何将任何数组传递给 sql server 而无需创建新表类型的所有示例和答案,直到我找到了这个linK,以下是我如何将它应用到我的项目中:
--以下代码将获取一个数组作为参数并插入该数组的值 --array 到另一个表中
Create Procedure Proc1
@UserId int, //just an Id param
@s nvarchar(max) //this is the array your going to pass from C# code to your Sproc
AS
declare @xml xml
set @xml = N'<root><r>' + replace(@s,',','</r><r>') + '</r></root>'
Insert into UserRole (UserID,RoleID)
select
@UserId [UserId], t.value('.','varchar(max)') as [RoleId]
from @xml.nodes('//root/r') as a(t)
END
希望你喜欢它
【讨论】:
@zaitsman:清洁并不意味着最好或最合适。人们经常放弃灵活性和/或“适当的”复杂性和/或性能来获得“干净”的代码。这里的答案是“好的”,但仅适用于小型数据集。如果传入数组@s
是 CSV,那么简单地拆分它会更快(即 INSERT INTO...SELECT FROM SplitFunction)。转换为 XML 比 CLR 慢,而基于属性的 XML 无论如何要快得多。这是一个简单的列表,但传入 XML 或 TVP 也可以处理复杂的数组。不知道避免使用简单的一次性CREATE TYPE ... AS TABLE
会获得什么。【参考方案10】:
从 SQL Server 2016 开始,您可以简单地使用拆分字符串
例子:
WHERE (@LocationId IS NULL OR Id IN (SELECT items from Split_String(@LocationId, ',')))
【讨论】:
【参考方案11】:我花了很长时间才弄明白,所以以防万一有人需要...
这是基于 Aaron 回答中的 SQL 2005 方法,并使用他的 SplitInts 函数(我刚刚删除了 delim 参数,因为我总是使用逗号)。我正在使用 SQL 2008,但我想要一些适用于类型化数据集(XSD、TableAdapters)的东西,我知道字符串参数适用于这些。
我试图让他的函数在“where in (1,2,3)”类型的子句中工作,但没有运气直接的方式。所以我先创建了一个临时表,然后做了一个内部连接而不是“在哪里”。这是我的示例用法,在我的情况下,我想获取不包含某些成分的食谱列表:
CREATE PROCEDURE dbo.SOExample1
(
@excludeIngredientsString varchar(MAX) = ''
)
AS
/* Convert string to table of ints */
DECLARE @excludeIngredients TABLE (ID int)
insert into @excludeIngredients
select ID = Item from dbo.SplitInts(@excludeIngredientsString)
/* Select recipies that don't contain any ingredients in our excluded table */
SELECT r.Name, r.Slug
FROM Recipes AS r LEFT OUTER JOIN
RecipeIngredients as ri inner join
@excludeIngredients as ei on ri.IngredientID = ei.ID
ON r.ID = ri.RecipeID
WHERE (ri.RecipeID IS NULL)
【讨论】:
一般来说,最好不要加入表变量,而是加入临时表。默认情况下,表变量似乎只有一行,尽管有一两个技巧(查看@AaronBertrand 的优秀而详细的文章:sqlperformance.com/2014/06/t-sql-queries/…)。【参考方案12】:CREATE TYPE dumyTable
AS TABLE
(
RateCodeId int,
RateLowerRange int,
RateHigherRange int,
RateRangeValue int
);
GO
CREATE PROCEDURE spInsertRateRanges
@dt AS dumyTable READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT tblRateCodeRange(RateCodeId,RateLowerRange,RateHigherRange,RateRangeValue)
SELECT *
FROM @dt
END
【讨论】:
【参考方案13】:从 SQL Server 2016 开始,您可以将列表作为 NVARCHAR() 引入并使用 OPENJSON
DECLARE @EmployeeList nvarchar(500) = '[1,2,15]'
SELECT *
FROM Employees
WHERE ID IN (SELECT VALUE FROM OPENJSON(@EmployeeList ))
【讨论】:
以上是关于如何将数组传递到 SQL Server 存储过程的主要内容,如果未能解决你的问题,请参考以下文章
如何将 .NET 对象集合(父子)层次结构传递给 SQL Server 存储过程
是否可以在 Sql Server 中使用整数数组参数创建 sp? [复制]