【问题标题】:Passing a varchar full of comma delimited values to a SQL Server IN function将充满逗号分隔值的 varchar 传递给 SQL Server IN 函数
【发布时间】:2010-10-27 02:27:36
【问题描述】:

重复
Dynamic SQL Comma Delimited Value Query
Parameterized Queries with Like and In

我有一个 SQL Server 存储过程,我想将一个充满逗号分隔值的 varchar 传递给 IN 函数。例如:

DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';

SELECT * 
FROM sometable 
WHERE tableid IN (@Ids);

这当然行不通。我得到了错误:

将 varchar 值 '1,2,3,5,4,6,7,98,234' 转换为数据类型 int 时转换失败。

如何在不借助构建动态 SQL 的情况下完成此(或类似的操作)?

【问题讨论】:

  • 反对动态 SQL 有什么特别的原因吗?
  • 动态 SQL 让您更容易受到 SQL 注入攻击。
  • 您考虑过使用表值参数吗?
  • @HLGEM - 表值参数仅适用于 sql server 2008 及更高版本(仅在提出此问题的几个月前发布),但它似乎是实现此目的的最佳方法。
  • 最简单但可能会影响性能 - select * from sometable where CONVERT(varchar, tableid) in (@Ids)

标签: sql sql-server tsql sql-in


【解决方案1】:

这里有很多答案,但要加上我的两分钱,我认为STRING_SPLIT 是解决此类问题的一种非常简单的方法:

DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';

SELECT * 
FROM sometable 
WHERE tableid IN;
(SELECT value FROM STRING_SPLIT(@Ids, ','))

【讨论】:

    【解决方案2】:

    @RBarryYoung(上图)的回答对我有用。 但是当逗号分隔的字符串值之间有空格时,它将省略带空格的 ID。所以我删除了空格。

    看看下面的代码sn-p。

    Declare @Ids varchar(50) Set @Ids = '1   ,   2,3'
    set @Ids=','+Replace(@Ids,' ', '')+',';
    
    Select * from [tblEmployee]
    where Charindex(','+cast(ID as varchar(8000))+',', @Ids) > 0
    

    【讨论】:

      【解决方案3】:

      这对于我不想使用 CTE 也不想使用内部连接的需求之一非常有用。

      DECLARE @Ids varchar(50);
      SET @Ids = '1,2,3,5,4,6,7,98,234';
          
      SELECT
         cn1,cn2,cn3
      FROM tableName
      WHERE columnName in (select Value from fn_SplitString(@ids, ','))
      

      分割字符串的功能:

      CREATE FUNCTION [dbo].[fn_SplitString] ( @stringToSplit VARCHAR(MAX), @seperator Char )  
      RETURNS  
       @returnList TABLE ([Value] [nvarchar] (500))  
      AS  
      BEGIN  
        
       DECLARE @name NVARCHAR(255)  
       DECLARE @pos INT  
        
       WHILE CHARINDEX(@seperator, @stringToSplit) > 0  
       BEGIN  
        SELECT @pos  = CHARINDEX(@seperator, @stringToSplit)    
        SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)  
        
        INSERT INTO @returnList   
        SELECT @name  
        
        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)  
       END  
        
       INSERT INTO @returnList  
       SELECT @stringToSplit  
        
       RETURN  
      END  
      

      【讨论】:

        【解决方案4】:

        我遇到了同样的问题,我不想在源数据库上留下任何足迹 - 即没有存储过程或函数。我是这样处理的:

        declare @IDs table (Value int)
        
        insert into @IDs values(1)
        insert into @IDs values(2)
        insert into @IDs values(3)
        insert into @IDs values(5)
        insert into @IDs values(4)
        insert into @IDs values(6)
        insert into @IDs values(7)
        insert into @IDs values(98)
        insert into @IDs values(234)
        
        
        SELECT * 
        FROM sometable 
        WHERE tableid IN (select Value from @IDs)
        

        【讨论】:

          【解决方案5】:

          我发现最简单的方法是使用 FIND_IN_SET

          FIND_IN_SET(column_name, values)
          
          values=(1,2,3)
          
          SELECT name WHERE FIND_IN_SET(id, values)
          

          【讨论】:

          • MS SQL (T-SQL) 没有 FIND_IN_SET()
          【解决方案6】:
          CREATE TABLE t 
            ( 
               id   INT, 
               col1 VARCHAR(50) 
            ) 
          
          INSERT INTO t 
          VALUES     (1, 
                      'param1') 
          
          INSERT INTO t 
          VALUES     (2, 
                      'param2') 
          
          INSERT INTO t 
          VALUES     (3, 
                      'param3') 
          
          INSERT INTO t 
          VALUES     (4, 
                      'param4') 
          
          INSERT INTO t 
          VALUES     (5, 
                      'param5') 
          
          DECLARE @params VARCHAR(100) 
          
          SET @params = ',param1,param2,param3,' 
          
          SELECT * 
          FROM   t 
          WHERE  Charindex(',' + Cast(col1 AS VARCHAR(8000)) + ',', @params) > 0 
          

          工作小提琴在这里找到Fiddle

          【讨论】:

          • 这与 2009 年 here 发布的方法相同。
          【解决方案7】:

          试试这个:

          SELECT ProductId, Name, Tags  
          FROM Product  
          WHERE '1,2,3,' LIKE '%' + CAST(ProductId AS VARCHAR(20)) + ',%'; 
          

          正如this link的最后一个例子所说

          【讨论】:

          • id > 10 时不起作用,例如 DECLARE @Ids NVARCHAR(1000) = '3,4,5,6,7,8,9,10,11,12,'。它得到所有 1,2 & 11, 12
          【解决方案8】:
          Error 493: The column 'i' that was returned from the nodes() method cannot be 
             used directly. It can only be used with one of the four XML data type 
             methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT 
             NULL checks.
          

          上述错误已在 SQL Server 2014 中通过使用以下 sn-p 修复

          Declare @Ids varchar(50)
          Set @Ids = '1,2,3,5,4,6,7,98,234'
          
          DECLARE @XML XML
          SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)
          
          SELECT SomeTable.* 
          FROM
              SomeTable 
              cross apply @XML.nodes('i') x(i) 
                  where SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')
          

          【讨论】:

            【解决方案9】:

            我认为一个非常简单的解决方案可能如下:

            DECLARE @Ids varchar(50);
            SET @Ids = '1,2,3,5,4,6,7,98,234';
            
            SELECT * 
            FROM sometable 
            WHERE ','+@Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';
            

            【讨论】:

            • 你能解释一下吗?
            • 很明显like操作符是用来过滤记录的,我一般在这种场景下用了很久。它非常简单易懂。
            • 这对我来说很好,因为我有一个场景,我不想向数据库添加任何新函数,并且我正在使用不支持 STRING_SPLIT 的旧版本。
            【解决方案10】:

            创建如下表函数,解析逗号分隔的 varchar 并返回一个可以与其他表进行内连接的表。

            CREATE FUNCTION [dbo].[fn_SplitList]
            (
              @inString     varchar(MAX)  = '',
              @inDelimiter  char(1)       = ',' -- Keep the delimiter to 100 chars or less.  Generally a delimiter will be 1-2 chars only.
            )
            RETURNS @tbl_Return  table
            (
              Unit  varchar(1000) COLLATE Latin1_General_BIN
            )
            AS
            BEGIN 
                INSERT INTO @tbl_Return
                SELECT DISTINCT
                  LTRIM(RTRIM(piece.value('./text()[1]', 'varchar(1000)'))) COLLATE DATABASE_DEFAULT AS Unit
                FROM
                (
                  --
                  --  Replace any delimiters in the string with the "X" tag.
                  --
                  SELECT
                    CAST(('<X>' + REPLACE(s0.prsString, s0.prsSplitDelimit, '</X><X>') + '</X>') AS xml).query('.') AS units
                  FROM
                  (
                    --
                    --  Convert the string and delimiter into XML.
                    --
                    SELECT
                      (SELECT @inString FOR XML PATH('')) AS prsString,
                      (SELECT @inDelimiter FOR XML PATH('')) AS prsSplitDelimit
                  ) AS s0
                ) AS s1
                CROSS APPLY units.nodes('X') x(piece)
              RETURN
            END
            

            ================================================ == 现在在您的代码中使用上面创建的表函数,函数的创建是您数据库中的一次性活动,可以跨数据库使用,也可以在同一服务器上使用。

            DECLARE @Ids varchar(50);
            SET @Ids = '1,2,3,5,4,6,7,98,234';
            
            SELECT
                 *
            FROM sometable AS st
            INNER JOIN fn_SplitList(@ids, ',') AS sl
                 ON sl.unit = st.tableid
            

            【讨论】:

              【解决方案11】:

              我对用户 KM 有同样的想法。但不需要额外的表号。仅此功能。

              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
                  DECLARE @number int = 0
                  DECLARE @childString varchar(502) = ''
                  DECLARE @lengthChildString int = 0
                  DECLARE @processString varchar(502) = @SplitOn + @List + @SplitOn
              
                  WHILE @number < LEN(@processString)
                  BEGIN
                      SET @number = @number + 1
                      SET @lengthChildString = CHARINDEX(@SplitOn, @processString, @number + 1) - @number - 1
                      IF @lengthChildString > 0
                      BEGIN
                          SET @childString = LTRIM(RTRIM(SUBSTRING(@processString, @number + 1, @lengthChildString)))
              
                          IF @childString IS NOT NULL AND @childString != ''
                          BEGIN
                              INSERT INTO @ParsedList(ListValue) VALUES (@childString)
                              SET @number = @number + @lengthChildString - 1
                          END
                      END
                  END
              
              RETURN
              
              END
              

              这是测试:

              SELECT ListValue FROM dbo.FN_ListToTable('/','a/////bb/c')
              

              结果:

                 ListValue
              ______________________
                 a
                 bb
                 c
              

              【讨论】:

                【解决方案12】:

                已经有一段时间了,但我过去曾使用 XML 作为过渡。

                我对此不以为然,但恐怕我不再知道我从哪里得到这个想法:

                -- declare the variables needed
                DECLARE @xml as xml,@str as varchar(100),@delimiter as varchar(10)
                
                -- The string you want to split
                SET @str='A,B,C,D,E,Bert,Ernie,1,2,3,4,5'
                
                -- What you want to split on. Can be a single character or a string
                SET @delimiter =','
                
                -- Convert it to an XML document
                SET @xml = cast(('<X>'+replace(@str,@delimiter ,'</X><X>')+'</X>') as xml)
                
                -- Select back from the XML
                SELECT N.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as T(N)
                

                【讨论】:

                • 我想我看你评论的答案有点厚,但是很难把它变成一个 IN 子句。使用这个例子有帮助。谢谢!
                【解决方案13】:

                我可以建议像这样使用WITH

                DECLARE @Delim char(1) = ',';
                SET @Ids = @Ids + @Delim;
                
                WITH CTE(i, ls, id) AS (
                    SELECT 1, CHARINDEX(@Delim, @Ids, 1), SUBSTRING(@Ids, 1, CHARINDEX(@Delim, @Ids, 1) - 1)
                    UNION ALL
                    SELECT i + 1, CHARINDEX(@Delim, @Ids, ls + 1), SUBSTRING(@Ids, ls + 1, CHARINDEX(@Delim, @Ids, ls + 1) - CHARINDEX(@Delim, @Ids, ls) - 1)
                    FROM CTE
                    WHERE  CHARINDEX(@Delim, @Ids, ls + 1) > 1
                )
                SELECT t.*
                FROM yourTable t
                    INNER JOIN
                    CTE c
                    ON t.id = c.id;
                

                【讨论】:

                • 太棒了。我在 CTE id 上为 int 添加了一个强制转换,用于加入我的表的唯一标识符。
                【解决方案14】:

                如果您使用 SQL Server 2008 或更高版本,请使用表值参数;例如:

                CREATE PROCEDURE [dbo].[GetAccounts](@accountIds nvarchar)
                AS
                BEGIN
                    SELECT * 
                    FROM accountsTable 
                    WHERE accountId IN (select * from @accountIds)
                END
                
                CREATE TYPE intListTableType AS TABLE (n int NOT NULL)
                
                DECLARE @tvp intListTableType 
                
                -- inserts each id to one row in the tvp table    
                INSERT @tvp(n) VALUES (16509),(16685),(46173),(42925),(46167),(5511)
                
                EXEC GetAccounts @tvp
                

                【讨论】:

                  【解决方案15】:

                  我以前写过一个存储过程来展示如何做到这一点。 您基本上必须处理字符串。 我试图在这里发布代码,但格式变得很糟糕。

                  IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[uspSplitTextList]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
                     DROP PROCEDURE [dbo].[uspSplitTextList]
                  GO
                  
                  SET QUOTED_IDENTIFIER ON 
                  GO
                  SET ANSI_NULLS ON 
                  GO
                  
                  
                  /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
                  -- uspSplitTextList
                  --
                  -- Description:
                  --    splits a separated list of text items and returns the text items
                  --
                  -- Arguments:
                  --    @list_text        - list of text items
                  --    @Delimiter        - delimiter
                  --
                  -- Notes:
                  -- 02/22/2006 - WSR : use DATALENGTH instead of LEN throughout because LEN doesn't count trailing blanks
                  --
                  -- History:
                  -- 02/22/2006 - WSR : revised algorithm to account for items crossing 8000 character boundary
                  -- 09/18/2006 - WSR : added to this project
                  --
                  CREATE PROCEDURE uspSplitTextList
                     @list_text           text,
                     @Delimiter           varchar(3)
                  AS
                  
                  SET NOCOUNT ON
                  
                  DECLARE @InputLen       integer         -- input text length
                  DECLARE @TextPos        integer         -- current position within input text
                  DECLARE @Chunk          varchar(8000)   -- chunk within input text
                  DECLARE @ChunkPos       integer         -- current position within chunk
                  DECLARE @DelimPos       integer         -- position of delimiter
                  DECLARE @ChunkLen       integer         -- chunk length
                  DECLARE @DelimLen       integer         -- delimiter length
                  DECLARE @ItemBegPos     integer         -- item starting position in text
                  DECLARE @ItemOrder      integer         -- item order in list
                  DECLARE @DelimChar      varchar(1)      -- first character of delimiter (simple delimiter)
                  
                  -- create table to hold list items
                  -- actually their positions because we may want to scrub this list eliminating bad entries before substring is applied
                  CREATE TABLE #list_items ( item_order integer, item_begpos integer, item_endpos integer )
                  
                  -- process list
                  IF @list_text IS NOT NULL
                     BEGIN
                  
                     -- initialize
                     SET @InputLen = DATALENGTH(@list_text)
                     SET @TextPos = 1
                     SET @DelimChar = SUBSTRING(@Delimiter, 1, 1)
                     SET @DelimLen = DATALENGTH(@Delimiter)
                     SET @ItemBegPos = 1
                     SET @ItemOrder = 1
                     SET @ChunkLen = 1
                  
                     -- cycle through input processing chunks
                     WHILE @TextPos <= @InputLen AND @ChunkLen <> 0
                        BEGIN
                  
                        -- get current chunk
                        SET @Chunk = SUBSTRING(@list_text, @TextPos, 8000)
                  
                        -- setup initial variable values
                        SET @ChunkPos = 1
                        SET @ChunkLen = DATALENGTH(@Chunk)
                        SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)
                  
                        -- loop over the chunk, until the last delimiter
                        WHILE @ChunkPos <= @ChunkLen AND @DelimPos <> 0
                           BEGIN
                  
                           -- see if this is a full delimiter
                           IF SUBSTRING(@list_text, (@TextPos + @DelimPos - 1), @DelimLen) = @Delimiter
                              BEGIN
                  
                              -- insert position
                              INSERT INTO #list_items (item_order, item_begpos, item_endpos)
                              VALUES (@ItemOrder, @ItemBegPos, (@TextPos + @DelimPos - 1) - 1)
                  
                              -- adjust positions
                              SET @ItemOrder = @ItemOrder + 1
                              SET @ItemBegPos = (@TextPos + @DelimPos - 1) + @DelimLen
                              SET @ChunkPos = @DelimPos + @DelimLen
                  
                              END
                           ELSE
                              BEGIN
                  
                              -- adjust positions
                              SET @ChunkPos = @DelimPos + 1
                  
                              END
                  
                           -- find next delimiter      
                           SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)
                  
                           END
                  
                        -- adjust positions
                        SET @TextPos = @TextPos + @ChunkLen
                  
                        END
                  
                     -- handle last item
                     IF @ItemBegPos <= @InputLen
                        BEGIN
                  
                        -- insert position
                        INSERT INTO #list_items (item_order, item_begpos, item_endpos)
                        VALUES (@ItemOrder, @ItemBegPos, @InputLen)
                  
                        END
                  
                     -- delete the bad items
                     DELETE FROM #list_items
                     WHERE item_endpos < item_begpos
                  
                     -- return list items
                     SELECT SUBSTRING(@list_text, item_begpos, (item_endpos - item_begpos + 1)) AS item_text, item_order, item_begpos, item_endpos
                     FROM #list_items
                     ORDER BY item_order
                  
                     END
                  
                  DROP TABLE #list_items
                  
                  RETURN
                  
                  /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
                  
                  GO
                  SET QUOTED_IDENTIFIER OFF 
                  GO
                  SET ANSI_NULLS ON 
                  GO
                  

                  【讨论】:

                  • 您的答案有一些断开的链接...您可以检查一下吗?
                  • 按要求添加了代码,尽管我不确定我是否再使用此算法。前段时间我切换到传递xml,然后使用sql的xml支持。
                  【解决方案16】:

                  你可以这样做:

                  create or replace 
                  PROCEDURE UDP_SETBOOKMARK 
                  (
                    P_USERID IN VARCHAR2  
                  , P_BOOKMARK IN VARCHAR2  
                  ) AS 
                  BEGIN
                  
                  UPDATE T_ER_Bewertung
                  SET LESEZEICHEN = P_BOOKMARK
                  WHERE STAMM_ID in( select regexp_substr(P_USERID,'[^,]+', 1, level) from dual
                                     connect by regexp_substr(P_USERID, '[^,]+', 1, level) is not null )
                  and ER_ID = (select max(ER_ID) from T_ER_Bewertung_Kopie);
                  
                  commit;
                  
                  END UDP_SETBOOKMARK;
                  

                  那就试试吧

                  Begin
                  UDP_SETBOOKMARK ('1,2,3,4,5', 'Test');
                  End;
                  

                  您也可以在其他情况下将此 IN-Clause 与 regexp_substr 一起使用,试试吧。

                  【讨论】:

                    【解决方案17】:

                    最佳且简单的方法。

                    DECLARE @AccumulateKeywordCopy NVARCHAR(2000),@IDDupCopy NVARCHAR(50);
                    SET @AccumulateKeywordCopy ='';
                    SET @IDDupCopy ='';
                    SET @IDDup = (SELECT CONVERT(VARCHAR(MAX), <columnName>) FROM <tableName> WHERE <clause>)
                    
                    SET @AccumulateKeywordCopy = ','+@AccumulateKeyword+',';
                    SET @IDDupCopy = ','+@IDDup +',';
                    SET @IDDupCheck = CHARINDEX(@IDDupCopy,@AccumulateKeywordCopy)
                    

                    【讨论】:

                      【解决方案18】:

                      如果不使用动态 SQL,您必须获取输入变量并使用拆分函数将数据放入临时表中,然后加入该表。

                      【讨论】:

                        【解决方案19】:

                        当然,如果你像我一样懒惰,你可以这样做:

                        Declare @Ids varchar(50) Set @Ids = ',1,2,3,5,4,6,7,98,234,'
                        
                        Select * from sometable
                         where Charindex(','+cast(tableid as varchar(8000))+',', @Ids) > 0
                        

                        【讨论】:

                        • 我使用了这种方法,它运行良好,直到我部署到有 450 万行的实时服务器,此时它太慢了。始终考虑可扩展性!
                        • @CeejeeB 已经考虑过了。请注意“lazy”这个词,当我关心性能、可扩展性、维护或可支持性时,我的做法类似于 KM. 的回答。即,正确的方式。
                        • @RBarryYoung 这是一个很好的创意解决方案,我赞了。虽然我从不喜欢看到 CharIndex(..)>0,但我能想到的最语义和可读的替代方法是使用 LIKE 来知道它是否包含字符串 =) 干杯!跨度>
                        • 原因是在 where 语句中使用函数将使该语句成为非 sargable 意味着它将导致扫描。
                        • 这种穷人的做法正是我想要的。我不想创建自定义函数(因为原因),我只处理一年中生成一组内存中的天数(内存中的 365-366 条记录)来每年填充一次配置表。太棒了! (是的,我知道这是一个非常古老的答案,但仍然,谢谢!)
                        【解决方案20】:
                        -- select * from dbo.Split_ID('77,106')  
                        
                            ALTER FUNCTION dbo.Split_ID(@String varchar(8000))     
                            returns @temptable TABLE (ID varchar(8000))     
                            as     
                            begin     
                                declare @idx int     
                                declare @slice varchar(8000)     
                                declare @Delimiter char(1)
                                 set @Delimiter =','
                        
                                select @idx = 1     
                                    if len(@String)<1 or @String is null  return     
                        
                                while @idx!= 0     
                                begin     
                                    set @idx = charindex(@Delimiter,@String)     
                                    if @idx!=0     
                                        set @slice = left(@String,@idx - 1)     
                                    else     
                                        set @slice = @String     
                        
                                    if(len(@slice)>0)
                                        insert into @temptable(ID) values(@slice)     
                        
                                    set @String = right(@String,len(@String) - @idx)     
                                    if len(@String) = 0 break     
                                end 
                            return     
                            end
                        

                        【讨论】:

                          【解决方案21】:

                          感谢您的功能,我使用了它............ 这是我的例子

                          **UPDATE [RD].[PurchaseOrderHeader]
                          SET     [DispatchCycleNumber] ='10'
                           WHERE  OrderNumber in(select * FROM XA.fn_SplitOrderIDs(@InvoiceNumberList))**
                          
                          
                          CREATE FUNCTION [XA].[fn_SplitOrderIDs]
                          (
                              @OrderList varchar(500)
                          )
                          RETURNS 
                          @ParsedList table
                          (
                              OrderID int
                          )
                          AS
                          BEGIN
                              DECLARE @OrderID varchar(10), @Pos int
                          
                              SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
                              SET @Pos = CHARINDEX(',', @OrderList, 1)
                          
                              IF REPLACE(@OrderList, ',', '') <> ''
                              BEGIN
                                  WHILE @Pos > 0
                                  BEGIN
                                          SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
                                          IF @OrderID <> ''
                                          BEGIN
                                                  INSERT INTO @ParsedList (OrderID) 
                                                  VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
                                          END
                                          SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
                                          SET @Pos = CHARINDEX(',', @OrderList, 1)
                          
                                  END
                              END 
                              RETURN
                          END
                          

                          【讨论】:

                            【解决方案22】:

                            这很完美!下面的答案太复杂了。不要将其视为动态的。如下设置您的存储过程:

                            (@id as varchar(50))
                            as
                            
                            Declare @query as nvarchar(max)
                            set @query ='
                            select * from table
                            where id in('+@id+')'
                            EXECUTE sp_executesql @query
                            

                            【讨论】:

                            • 不明智....试试这个:SET @id = '0); SELECT ''嗨,我刚刚冲洗了你的服务器...''--'
                            • 啊,注射。但这通常只适用于允许用户输入的情况。
                            • 除了安全性之外,从性能的角度来看,使用连接文字也不是一个好主意:每次使用不同的值执行 SQL 语句时,连接文字将在查询计划缓存中创建重复的查询计划在@id。如果这是一个繁忙的服务器,说“hola”来查询计划缓存膨胀(参考。mssqltips.com/sqlservertip/2681/…
                            【解决方案23】:

                            无表无函数无循环

                            基于将您的列表解析为我们的 DBA 建议使用 XML 的表格的想法。

                            Declare @Ids varchar(50)
                            Set @Ids = ‘1,2,3,5,4,6,7,98,234’
                            
                            DECLARE @XML XML
                            SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)
                            
                            SELECT * 
                            FROM
                                SomeTable 
                                INNER JOIN @XML.nodes('i') x(i) 
                                    ON  SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')
                            

                            这些似乎与@KM 的答案具有相同的性能,但我认为要简单得多。

                            【讨论】:

                            • 这是其他人告诉我要使用的.. 你能解释一下INNER JOIN @XML.nodes('i') x(i) ON SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)') 部分吗?抱歉,我对此很陌生。
                            • @PeterPitLock - 是的,请参阅下面的答案。您可以像使用任何其他表一样使用 xml
                            • 对我不起作用。使用 CategoryID 尝试使用 Northwind 的 Categories 表,我得到的是错误:Error 493: The column 'i' that was returned from the nodes() method cannot be used directly. It can only be used with one of the four XML data type methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT NULL checks.
                            • @Matt 我也明白了。尝试用 SELECT SomeTable.* 替换 SELECT *,它应该可以工作。
                            • @Matt - 我试过了,但是我得到了一个不同的错误:Error 207: Invalid column name 'Id'.
                            【解决方案24】:

                            不要使用循环的函数来拆分字符串!,我下面的函数会非常快地拆分字符串,没有循环!

                            在你使用我的功能之前,你需要建立一个“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 FN_ListToTable(',',@YourString) s ON  YourTable.ID = s.ListValue
                            

                            这是你的例子:

                            Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable(',',@Ids) s)
                            

                            【讨论】:

                            • 当您执行 Select 语句时,您认为查询处理器在做什么? - 使用跨时间量子物理学瞬时生成所有行?它也在循环...您只是从一个您明确控制的循环更改为一个 SQL Server 查询处理器控制...
                            • @Charles Bretana,哈!你可以用 10 种不同的方式编写代码,每种方式的执行方式都不同(速度方面)。目标是以最快的方式编写它。试试看,针对另一个问题中列出的存储过程循环方法运行此拆分方法。每次运行 100 次,看看它们需要多长时间。 ----- 仅供参考,我确信 SQL Server 内部循环比用户创建的存储过程更快、更优化,具有局部变量和 WHILE 循环!
                            • 你有超过8000个字符的解决方案吗?我需要的一些地方已经达到了 8000 个字符的限制,所以我编写了上面链接的实现。
                            • @Will Rickards,如果您需要处理 >8k 的字符串,您可以使用 CLR (sommarskog.se/arrays-in-sql.html) 使循环更快,或者更改循环以处理 8k 的块(确保您中断逗号),但是将这些块传递给像我这样的函数。
                            • 查尔斯和公里。您的每个 cmets 都有一些优点。是的,SQL 引擎会在某个时候循环遍历各个数字。但是引擎的循环可能会比用户编写的循环运行得快得多。首先要避免循环的真正解决方案是重新设计模式以符合第一范式。 CSV 字段看起来像 1NF,但它不是真正的 1NF。这才是真正的问题。
                            【解决方案25】:

                            这是一个很常见的问题。固定答案,几个不错的技巧:

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

                            【讨论】:

                            • 链接页面确实有一些很棒的信息,特别是如果你想走 CLR 路线。
                            【解决方案26】:

                            您可以创建一个返回表的函数。

                            所以你的陈述会像

                            select * from someable 
                             join Splitfunction(@ids) as splits on sometable.id = splits.id
                            

                            这是一个模拟函数。

                            CREATE FUNCTION [dbo].[FUNC_SplitOrderIDs]
                            (
                                @OrderList varchar(500)
                            )
                            RETURNS 
                            @ParsedList table
                            (
                                OrderID int
                            )
                            AS
                            BEGIN
                                DECLARE @OrderID varchar(10), @Pos int
                            
                                SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
                                SET @Pos = CHARINDEX(',', @OrderList, 1)
                            
                                IF REPLACE(@OrderList, ',', '') <> ''
                                BEGIN
                                    WHILE @Pos > 0
                                    BEGIN
                                        SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
                                        IF @OrderID <> ''
                                        BEGIN
                                            INSERT INTO @ParsedList (OrderID) 
                                            VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
                                        END
                                        SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
                                        SET @Pos = CHARINDEX(',', @OrderList, 1)
                            
                                    END
                                END 
                                RETURN
                            END
                            

                            【讨论】:

                            猜你喜欢
                            • 2023-03-30
                            • 2016-06-22
                            • 1970-01-01
                            • 1970-01-01
                            • 2019-08-29
                            • 1970-01-01
                            • 1970-01-01
                            • 2013-01-10
                            • 2015-12-29
                            相关资源
                            最近更新 更多