如何将数组传递给 SQL Server 存储过程?
例如,我有一个员工列表。我想将此列表用作表格并将其与另一个表格连接。但是员工列表应该作为 C# 的参数传递。
如何将数组传递给 SQL Server 存储过程?
例如,我有一个员工列表。我想将此列表用作表格并将其与另一个表格连接。但是员工列表应该作为 C# 的参数传递。
首先,在您的数据库中,创建以下两个对象:
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,我仍然建议使用 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 应该代表什么上下文,类型名称应该是通用的。
根据我的经验,通过从employeeIDs 创建一个分隔的表达式,这个问题有一个棘手而好的解决方案。您应该只创建一个字符串表达式,例如';123;434;365;'
in-which 123
,434
并且365
是一些employeeID。通过调用以下过程并将此表达式传递给它,您可以获取所需的记录。您可以轻松地将“另一个表”加入此查询。此解决方案适用于所有版本的 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
为您的存储过程使用表值参数。
当您从 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(
"@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
}
您需要将其作为 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
上下文总是很重要的,例如数组的大小和复杂性。对于中小型列表,此处发布的几个答案都很好,但应进行一些澄清:
text()
XML 函数。有关使用 XML 拆分列表的更多信息(即性能分析),请查看Phil Factor 的“使用 XML 将列表作为 SQL Server 中的参数传递” 。DataTable
存储在内存中意味着复制内存中的数据,因为它是从原始集合中复制的。因此,使用DataTable
传入 TVP 的方法不适用于较大的数据集(即不能很好地扩展)。DataTable
TVP 方法一样,XML 不能很好地扩展,因为它需要将内存中的数据大小增加一倍以上,因为它需要额外考虑 XML 文档的开销。综上所述,如果您使用的数据很大或不是很大但一直在增长,那么IEnumerable
TVP 方法是最佳选择,因为它将数据流式传输到 SQL Server(如该DataTable
方法),但不是需要在内存中复制任何集合(与任何其他方法不同)。我在这个答案中发布了一个 SQL 和 C# 代码示例:
正如上面其他人所指出的,一种方法是将数组转换为字符串,然后在 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
作为你的参数传递给上面的存储过程。
sql server 中不支持数组,但有几种方法可以将集合传递给存储的 proc。
以下链接可能对您有所帮助
这将对您有所帮助。:) 按照接下来的步骤,
打开查询编辑器
复制粘贴下面的代码,它将创建将字符串转换为 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
执行此 SP 使用exec sp_DeleteId '1,2,3,12'
这是您要删除的 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 ;
这将删除单个存储过程调用中的多行。祝一切顺利。
我一直在搜索如何将任何数组传递给 sql server 而无需创建新的 Table 类型的所有示例和答案,直到我找到这个链接,下面是我如何将它应用到我的项目中:
--以下代码将获取一个数组作为参数并将该数组的值插入到另一个表中
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
希望你喜欢它
从 SQL Server 2016 开始,您可以简单地使用拆分字符串
例子:
WHERE (@LocationId IS NULL OR Id IN (SELECT items from Split_String(@LocationId, ',')))
我花了很长时间才弄清楚这一点,所以以防万一有人需要...
这是基于 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)
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
从 SQL Server 2016 开始,您可以将列表作为 NVARCHAR() 并使用 OPENJSON
DECLARE @EmployeeList nvarchar(500) = '[1,2,15]'
SELECT *
FROM Employees
WHERE ID IN (SELECT VALUE FROM OPENJSON(@EmployeeList ))