【问题标题】:Passing an array of parameters to a stored procedure将参数数组传递给存储过程
【发布时间】:2010-11-07 08:18:14
【问题描述】:

我需要将一个“id”数组传递给一个存储过程,以从表中删除所有行,除了数组中与 id 匹配的行。

我怎样才能以最简单的方式做到这一点?

【问题讨论】:

  • 这不是stackoverflow.com/questions/114504/…的副本吗?
  • @John Saunders,有很多“将数组作为参数传递”的 sql server 问题。但是,这有一个额外的转折,即删除除了问题的传入参数部分之外的所有行。结果,我不认为它是重复的。
  • John Saunders,我不知道我进行了搜索,但没有找到我想要的东西。这有什么问题吗?
  • 如果你要进行大量的过程调用,并且每次都需要构建一个xml字符串,那么你效率不高,纯sql方法会更好。如果您已经有了 xml 字符串,或者正在进行少量的过程调用,那么 xml 就可以了。

标签: sql sql-server-2005 stored-procedures


【解决方案1】:

如果您使用的是 Sql Server 2008 或更高版本,则可以使用称为表值参数 (TVP) 的东西,而不是每次要将列表数据传递给存储过程时对其进行序列化和反序列化。

让我们首先创建一个简单的模式作为我们的游乐场:

CREATE DATABASE [TestbedDb]
GO


USE [TestbedDb]
GO

    /* First, setup the sample program's account & credentials*/
CREATE LOGIN [testbedUser] WITH PASSWORD=N'µ×?
?S[°¿Q­¥½q?_Ĭ¼Ð)3õļ%dv', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=ON
GO

CREATE USER [testbedUser] FOR LOGIN [testbedUser] WITH DEFAULT_SCHEMA=[dbo]
GO

EXEC sp_addrolemember N'db_owner', N'testbedUser'
GO


    /* Now setup the schema */
CREATE TABLE dbo.Table1 ( t1Id INT NOT NULL PRIMARY KEY );
GO

INSERT INTO dbo.Table1 (t1Id)
VALUES
    (1),
    (2),
    (3),
    (4),
    (5),
    (6),
    (7),
    (8),
    (9),
    (10);
GO

有了我们的架构和示例数据,我们现在可以创建我们的 TVP 存储过程:

CREATE TYPE T1Ids AS Table (
        t1Id INT
);
GO


CREATE PROCEDURE dbo.FindMatchingRowsInTable1( @Table1Ids AS T1Ids READONLY )
AS
BEGIN
        SET NOCOUNT ON;

        SELECT Table1.t1Id FROM dbo.Table1 AS Table1
        JOIN @Table1Ids AS paramTable1Ids ON Table1.t1Id = paramTable1Ids.t1Id;
END
GO

有了我们的架构和 API,我们可以从我们的程序中调用 TVP 存储过程,如下所示:

        // Curry the TVP data
        DataTable t1Ids = new DataTable( );
        t1Ids.Columns.Add( "t1Id",
                           typeof( int ) );

        int[] listOfIdsToFind = new[] {1, 5, 9};
        foreach ( int id in listOfIdsToFind )
        {
            t1Ids.Rows.Add( id );
        }
        // Prepare the connection details
        SqlConnection testbedConnection =
                new SqlConnection(
                        @"Data Source=.\SQLExpress;Initial Catalog=TestbedDb;Persist Security Info=True;User ID=testbedUser;Password=letmein12;Connect Timeout=5" );

        try
        {
            testbedConnection.Open( );

            // Prepare a call to the stored procedure
            SqlCommand findMatchingRowsInTable1 = new SqlCommand( "dbo.FindMatchingRowsInTable1",
                                                                  testbedConnection );
            findMatchingRowsInTable1.CommandType = CommandType.StoredProcedure;

            // Curry up the TVP parameter
            SqlParameter sqlParameter = new SqlParameter( "Table1Ids",
                                                          t1Ids );
            findMatchingRowsInTable1.Parameters.Add( sqlParameter );

            // Execute the stored procedure
            SqlDataReader sqlDataReader = findMatchingRowsInTable1.ExecuteReader( );

            while ( sqlDataReader.Read( ) )
            {
                Console.WriteLine( "Matching t1ID: {0}",
                                   sqlDataReader[ "t1Id" ] );
            }
        }
        catch ( Exception e )
        {
            Console.WriteLine( e.ToString( ) );
        }
  /* Output:
   * Matching t1ID: 1
   * Matching t1ID: 5
   * Matching t1ID: 9
   */

使用更抽象的 API(例如 Entity Framework)可能是一种不那么痛苦的方法。不过,我现在没有时间亲眼看看。

【讨论】:

  • 我认为这是最好的方法。我很震惊你没有为此投票。所以我给了你一个。
  • 我喜欢这种方法。感谢分享。
  • 对于可重用性,我相信这是迄今为止最好的方法。考虑以下常见的实际应用程序:我有一个搜索,它生成一个包含唯一主键的结果集。然后我希望将该结果集传递给另一个操作(例如,检索 excel 电子表格的更多信息)。使用此方法,我可以轻松地从初始结果集中检索 ID 列表并将该列表传递到我的第二阶段存储过程,该过程接受单个参数“@IDs T1IDs READONLY”。因此,我避开了最初搜索的繁重负担。
【解决方案2】:

使用存储过程:

编辑: 序列化列表(或其他任何东西)的补充:

List<string> testList = new List<int>();

testList.Add(1);
testList.Add(2);
testList.Add(3);

XmlSerializer xs = new XmlSerializer(typeof(List<int>));
MemoryStream ms = new MemoryStream();
xs.Serialize(ms, testList);

string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray());

结果(可与 XML 参数一起使用):

<?xml version="1.0"?>
<ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <int>1</int>
  <int>2</int>
  <int>3</int>
</ArrayOfInt>

原帖:

将 XML 作为参数传递:

<ids>
    <id>1</id>
    <id>2</id>
</ids>

CREATE PROCEDURE [dbo].[DeleteAllData]
(
    @XMLDoc XML
)
AS
BEGIN

DECLARE @handle INT

EXEC sp_xml_preparedocument @handle OUTPUT, @XMLDoc

DELETE FROM
    YOURTABLE
WHERE
    YOUR_ID_COLUMN NOT IN (
        SELECT * FROM OPENXML (@handle, '/ids/id') WITH (id INT '.') 
    )
EXEC sp_xml_removedocument @handle

【讨论】:

  • +1 如果您的数据已经在 XML 结构中,这是一个很好的解决方案——当您添加构建 XML 的客户端开销时,它不会那么热。
  • 提到了 RolandTumble,这需要将我的 iput 数据数组(列表)转换为 XML,所以我想这不是我的最佳解决方案。
  • 你可以使用 Serialize 来做到这一点...使用 XML 比拆分字符串更可靠...
  • 你能解释一下如何序列化 List 或 arraylist 吗?我不想为 xml 序列化创建一个包装器对象。
  • 只是为了在此处添加更新。您需要使用 xpath 来获取整数列表: SELECT * FROM OPENXML(@xmlHandle, '/ArrayOfInt/int') WITH (id INT '.')
【解决方案3】:

这是最好的来源:

http://www.sommarskog.se/arrays-in-sql.html

使用链接创建一个拆分函数,并像这样使用它:

DELETE YourTable
    FROM YourTable                           d
    LEFT OUTER JOIN dbo.splitFunction(@Parameter) s ON d.ID=s.Value
    WHERE s.Value IS NULL

I prefer the number table approach

这是基于上述链接的代码,应该为您完成...

在你使用我的功能之前,你需要建立一个“helper”表,每个数据库只需要这样做一次:

CREATE TABLE Numbers
(Number int  NOT NULL,
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
    SET @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

使用这个函数来拆分你的字符串,它不会循环并且非常快:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn              char(1)              --REQUIRED, the character to split the @List string on
    ,@List                 varchar(8000)        --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
    ListValue varchar(500)
)
AS
BEGIN

/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.


Returns a table, one row per item in the list, with a column name "ListValue"

EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')

    returns:
        ListValue  
        -----------
        1
        12
        123
        1234
        54321
        6
        A
        *
        |||
        B

        (10 row(s) affected)

**/



----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
        (ListValue)
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''



RETURN

END --Function FN_ListToTable

您可以将此函数用作连接中的表:

SELECT
    Col1, COl2, Col3...
    FROM  YourTable
        INNER JOIN dbo.FN_ListToTable(',',@YourString) s ON  YourTable.ID = s.ListValue

这是你的删除:

DELETE YourTable
    FROM YourTable                                d
    LEFT OUTER JOIN dbo.FN_ListToTable(',',@Parameter) s ON d.ID=s.ListValue
    WHERE s.ListValue IS NULL

【讨论】:

  • @KM:为什么最多 8000 个字符?我们可以使用更长的字符串吗?
  • 您可以将输入参数设为 varchar(max),只要确保您的 Numbers 表有足够的行。我只使用 8000,因为我从不拆分长字符串。你可以看到我的另一个答案:stackoverflow.com/questions/4227552/… 我已经更新了 split 函数以使用表格函数并改进了 Numbers 表格的生成。
【解决方案4】:

你可以试试这个:



DECLARE @List VARCHAR(MAX)

SELECT @List = '1,2,3,4,5,6,7,8'

EXEC(
'DELETE
FROM TABLE
WHERE ID NOT IN (' + @List + ')'
)

【讨论】:

  • ...只要您确定 @List 参数不是从用户输入中填充的
  • 简单就是美丽。
【解决方案5】:
declare @ids nvarchar(1000)

set @ids = '100,2,3,4,5' --Parameter passed

set @ids = ',' + @ids + ','

select   *
from     TableName 
where    charindex(',' + CAST(Id as nvarchar(50)) + ',', @ids) > 0

【讨论】:

  • Bingo Vitalivs!一等奖是最少的代码,并在最短的时间内准确地产生所需的内容。嵌入的空格可能会在“@ids”中造成问题。
  • @Vitalivs 谢谢你的回答.. 它救了我:)
【解决方案6】:

您可以使用存储过程期望存在的临时表。这将适用于不支持 XML 等的旧版本 SQL Server。

CREATE TABLE #temp
(INT myid)
GO
CREATE PROC myproc
AS
BEGIN
    DELETE YourTable
    FROM YourTable                    
    LEFT OUTER JOIN #temp T ON T.myid=s.id
    WHERE s.id IS NULL
END

【讨论】:

    【解决方案7】:

    我会考虑将您的 ID 作为 XML 字符串传递,然后您可以将 XML 分解到一个临时表中以进行连接,或者您也可以使用 SP_XML_PREPAREDOCUMENTOPENXML 直接查询 XML。

    【讨论】:

      【解决方案8】:

      如果使用 XML 数据类型而不是传递数组呢?我发现这是一个更好的解决方案,并且在 SQL 2005 中运行良好

      【讨论】:

      • @Zanoni,为什么不信任拆分字符串?
      【解决方案9】:

      我喜欢这个,因为它适合作为XElement传递,适合SqlCommand

      (对不起,它是 VB.NET,但你明白了)

      <Extension()>
      Public Function ToXml(Of T)(array As IEnumerable(Of T)) As XElement
         Return XElement.Parse(
                 String.Format("<doc>{0}</doc>", String.Join("", array.Select(Function(s) String.Concat("<d>", s.ToString(), "</d>")))), LoadOptions.None)
       End Function
      

      这是sql Stored proc,缩短了,不完整!

      创建过程 [dbo].[myproc] (@blah xml)
      作为 ... WHERE SomeID IN (SELECT doc.t.value('.','int') from @netwerkids.nodes(N'/doc/d') as doc(t))

      【讨论】:

        【解决方案10】:

        在 SQL Server 2016 中,您可以使用 [ ] 包装数组并将其作为 JSON 传递,请参阅 http://blogs.msdn.com/b/sqlserverstorageengine/archive/2015/09/08/passing-arrays-to-t-sql-procedures-as-json.aspx

        【讨论】:

          【解决方案11】:

          您可以在 SQL Server 中使用 STRING_SPLIT 函数。 您可以查看文档here

          DECLARE @YourListOfIds VARCHAR(1000) -- Or VARCHAR(MAX) depending on what you need
          
          SET @YourListOfIds = '1,2,3,4,5,6,7,8'
          
          SELECT * FROM YourTable
          WHERE Id IN(SELECT CAST(Value AS INT) FROM STRING_SPLIT(@YourListOfIds, ','))
          

          【讨论】:

            猜你喜欢
            • 2016-04-05
            • 2021-12-29
            • 1970-01-01
            • 2016-04-27
            相关资源
            最近更新 更多