【问题标题】:How do I pass a list as a parameter in a stored procedure?如何在存储过程中将列表作为参数传递?
【发布时间】:2017-07-15 20:57:27
【问题描述】:

希望传递用户 ID 列表以返回列表名称。我计划处理输出的名称(使用 COALESCE 或其他名称),但试图找到传递用户 ID 列表的最佳方式。 我的 sproc 的内脏看起来像这样:

create procedure [dbo].[get_user_names]
@user_id_list, --which would equal a list of incoming ID numbers like (5,44,72,81,126)
@username varchar (30) output
as
select last_name+', '+first_name 
from user_mstr
where user_id in @user_id_list

传递@user_id_list 的值是我主要关心的问题。

【问题讨论】:

  • 我不会传入 ID 列表,而是创建一个用户定义的表类型,将值插入其中,然后将该变量传递给 proc。然后你可以对桌子做一个JOIN。
  • 我同意表参数...其次我会使用字符串拆分器,作为最后的手段...动态 sql。刚刚发布的类似问题here
  • 根据编程语言和 API,您可以将列表作为表值参数 (TVP) 传入。其他解决方案将根据您的 SQL Server 版本而有所不同。见sommarskog.se/arrays-in-sql.html。
  • 这取决于你如何称呼它。请编辑您的问题并描述您如何称呼它。 (来自 C#?内部 SQL?报告工具?)。另外请具体说明版本。部分版本可以使用string_split

标签: sql sql-server stored-procedures


【解决方案1】:

你可以试试这个:

create procedure [dbo].[get_user_names]
    @user_id_list varchar(2000), -- You can use any max length

    @username varchar (30) output
    as
    select last_name+', '+first_name 
    from user_mstr
    where user_id in (Select ID from dbo.SplitString( @user_id_list, ',') )

这里是 SplitString 的用户定义函数:

Create FUNCTION [dbo].[SplitString]
(    
      @Input NVARCHAR(MAX),
      @Character CHAR(1)
)
RETURNS @Output TABLE (
      Item NVARCHAR(1000)
)
AS
BEGIN
      DECLARE @StartIndex INT, @EndIndex INT

      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END

【讨论】:

【解决方案2】:

也许你可以使用:

select last_name+', '+first_name 
from user_mstr
where ',' + @user_id_list + ',' like '%,' + convert(nvarchar, user_id) + ',%'

【讨论】:

    【解决方案3】:

    在 SQL Server 中将值数组传递给存储过程的首选方法是使用表值参数。

    首先你像这样定义类型:

    CREATE TYPE UserList AS TABLE ( UserID INT );
    

    然后你在存储过程中使用那个类型:

    create procedure [dbo].[get_user_names]
    @user_id_list UserList READONLY,
    @username varchar (30) output
    as
    select last_name+', '+first_name 
    from user_mstr
    where user_id in (SELECT UserID FROM @user_id_list)
    

    所以在调用该存储过程之前,先填充一个表变量:

    DECLARE @UL UserList;
    INSERT @UL VALUES (5),(44),(72),(81),(126)
    

    最后调用 SP:

    EXEC dbo.get_user_names @UL, @username OUTPUT;
    

    【讨论】:

    • 当我尝试运行第一行时:“CREATE TYPE UserList AS TABLE (UserID INT);”我收到以下错误:“关键字 'AS' 附近的语法不正确。有什么建议吗?我使用的是 SQL Server 2014。
    • @CowboyBebop 尝试在 UserList 前面加上你的模式名称,通常是 dbo。所以该行变为: CREATE TYPE dbo.UserList AS TABLE ( UserID INT );
    • @CowboyBebop 您将在类型文件夹中创建 UserList。它不会在存储过程代码块中。
    【解决方案4】:

    据我所知,主要有三个竞争者:表值参数、分隔列表字符串和 JSON 字符串。

    自 2016 年以来,如果您想要分隔路由,可以使用内置的 STRING_SPLIT:https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

    这可能是最简单/最直接/最简单的方法。

    同样从 2016 年开始,JSON 可以作为 nvarchar 传递并与 OPENJSON 一起使用:https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql

    如果您有更结构化的数据集要传递,并且在其架构中可能会有很大的变化,那可能是最好的选择。

    TVP 似乎曾经是传递更多结构化参数的规范方式,如果您需要这种结构、明确性和基本的值/类型检查,它们仍然很好。不过,它们在消费者方面可能会更麻烦一些。如果您没有 2016+,这可能是默认/最佳选项。

    我认为这是在任何这些具体考虑因素以及您对明确说明参数结构的偏好之间进行权衡,这意味着即使您有 2016 年以上,您可能更愿意明确说明参数而不是传递一个字符串并以某种方式解析它。

    【讨论】:

    • groupby.org/conference-session-abstracts/author/amachanic STRING_SPLIT 是 2016 年或更高版本的最佳选择。它在性能方面破坏了本土代码。
    • 如果您使用的是 2016 或更高版本,但在与早期版本的兼容模式下运行,您可能仍然无法访问 STRING_SPLIT 或 OPENJSON(不幸的是,这是我的情况,尽管在 2019 年观点)。如何检查,请参阅:docs.microsoft.com/en-us/sql/relational-databases/databases/…(使用其中任何一种方法都需要兼容级别 130 或更高)
    • STRING_SPLIT 没有 sql 注入风险?
    • @JoelFan,不,这不会生成评估的动态 sql。 STRNG_SPLIT 生成一个单列表,其中每个拆分值作为一行。
    【解决方案5】:

    您可以使用这个简单的“内联”方法来构造 string_list_type 参数(适用于 SQL Server 2014):

    declare @p1 dbo.string_list_type
    insert into @p1 values(N'myFirstString')
    insert into @p1 values(N'mySecondString')
    

    执行存储过程时的示例使用:

    exec MyStoredProc @MyParam=@p1
    

    【讨论】:

      【解决方案6】:

      我通过以下方式解决了这个问题:

      1. 在 C# 中我构建了一个字符串变量。

      string userId="";

      1. 我将列表中的项目放入此变量中。我把','分开了。

      例如:在 C# 中

      userId= "5,44,72,81,126";
      

      并发送到 SQL-Server

       SqlParameter param = cmd.Parameters.AddWithValue("@user_id_list",userId);
      
      1. 我在 SQL-server 中创建分离函数,用于将接收到的列表(类型为 NVARCHAR(Max))转换为表。
      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  
        );
      
      1. 在主存储过程中,使用下面的命令,我使用条目列表。

      SELECT user_id = Item FROM dbo.SplitInts(@user_id_list, ',');

      【讨论】:

        【解决方案7】:

        Azure DB、Azure Data WH 以及从 SQL Server 2016 开始,您可以使用 STRING_SPLIT 来实现与 @sparrow 所描述的类似的结果。

        从@sparrow 回收代码

        WHERE user_id IN (SELECT value FROM STRING_SPLIT( @user_id_list, ',')
        

        将值列表接收到存储过程中的简单有效的方法

        【讨论】:

        【解决方案8】:

        检查下面的代码这对我有用

        @ManifestNoList      VARCHAR(MAX)
        
        
        WHERE
            (
                ManifestNo IN (SELECT value FROM dbo.SplitString(@ManifestNoList, ','))
            )
        

        【讨论】:

        • dbo.SplitString 哪里来的?我在 Azure Db 中没有它
        • dbo.SplitString 其在构建函数中
        • 您的意思是“内置”?根据this answer,您需要手动创建它stackoverflow.com/questions/33016022/…
        【解决方案9】:

        正确的方法是创建用户定义的数据类型:

        CREATE TYPE [dbo].[IntArray] AS TABLE
        (
            [ID] [INT] NULL
        )
        

        那么你就可以使用这个自定义数据类型了:

        CREATE OR ALTER PROCEDURE [dbo].[sp_GetUserNames]
        (
            @userIds [IntArray] READONLY
        )
        AS
        BEGIN
            SET NOCOUNT ON;
        
            SELECT 
                "Name" = u.LastName + ', ' + u.FirstName
            FROM dbo.User u
            JOIN @userIds uid ON u.Id = uid.Id;
        END
        

        用法:

        @DECLARE @result TABLE
        (
            Name NVARCHAR(max)
        );
        
        @DECLARE @ids [IntArray] = SELECT x.userId FROM dbo.sometable x;
        
        SET @result = EXECUTE [dbo].[sp_GetUserNames] @userIds = @ids;
        
        SELECT * FROM @result;
        

        【讨论】:

          【解决方案10】:

          这对我来说是完美的工作。这个完美的例子希望能解决很多用户的问题。

          第 1 步 像这样在sql中创建引用表

           Create TYPE dbo.tblNames
          AS TABLE
          (
              [Name] nvarchar(max)
          );
          go
          
          create TYPE dbo.tblNamesWithCols
          AS TABLE
          (
              [Name] nvarchar(max)
          );
          go
          

          第 2 步 使用像这样的引用表参数创建存储过程

              create proc syTest
              @VarTbleNameList AS dbo.tblNames READONLY,
              @VarTbleNameColsList AS dbo.tblNamesWithCols READONLY,
              @VarWhereQuery nvarchar(max)
              as
              begin
              ......
          ......  End
          

          **带参数调用存储过程**

          DECLARE @VarTbleList AS dbo.tblNames
          INSERT INTO @VarTbleList
          VALUES ( 'tblEmployes' )
          INSERT INTO @VarTbleList
          VALUES ( 'tblDepartments' )
          INSERT INTO @VarTbleList
          VALUES ( 'tblCities' )
          
          DECLARE @VarTbleColList AS dbo.tblNamesWithCols
          INSERT INTO @VarTbleColList
          VALUES ( 'tblEmployes.EmployeId as empId;' )
          INSERT INTO @VarTbleColList
          VALUES ( 'tblEmployes.EmployeName as empName;' )
          INSERT INTO @VarTbleColList
          VALUES ( 'tblDepartments.DepartmentName as deptName;'  )
          INSERT INTO @VarTbleColList
          VALUES ( 'tblDepartments.DepartmentId as deptId;' )
          
          EXECUTE  syTest @VarTbleList , @VarTbleColList , @VarWhereQuery ='test'
          

          【讨论】:

            猜你喜欢
            • 2016-03-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多