具有Like和In的动态SQL逗号分隔值查询参数化查询的重复


我有一个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和更高版本上可用(该版本仅在提出此问题之前的几个月才发布),但是看来这是实现此目的的最佳方法。

最简单,但可能会影响性能-从某个表中选择*,其中(@Ids)中的CONVERT(varchar,tableid)

#1 楼

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

在使用我的函数之前,您需要设置一个“助手” ”表,则每个数据库只需要执行一次此操作:

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)
09年5月18日在18:05

@查尔斯·布雷塔纳(Charles Bretana),哈!您可以用10种不同的方式编写代码,每种方式的执行方式都不同(速度方面)。目的是以最快的方式编写它。只需尝试一下,对另一个问题中列出的存储过程循环方法运行此split方法。每运行100次,看看它们需要多长时间。 -----仅供参考,我确信SQL Server内部循环比具有局部变量和WHILE循环的用户创建的存储过程要快得多,并且优化效果更好!

–KM。
09年5月18日在18:26

您有超过8000个字符的解决方案吗?我需要的几个地方都达到了8000个字符的限制,所以我写了上面链接的实现。

–里卡德斯
09年5月18日在19:21

@Will Rickards,如果您需要处理大于8k的字符串,则可以使用CLR(sommarskog.se/arrays-in-sql.html)加快循环速度,也可以更改循环以处理8k的块(确保中断) (以逗号开头),但将这些块传递给类似我的函数。

–KM。
09年5月18日在19:47

查尔斯和KM。您的每条评论都有其优点。是的,SQL引擎有时会遍历各个数字。但是引擎的循环可能比用户编写的循环快得多。为了避免首先循环,真正的解决方案是重新设计架构以符合第一范式。 CSV字段看起来像1NF,但实际上不是1NF。那是真正的问题。

– Walter Mitty
13年7月4日在11:09

#2 楼

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

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
2013年6月5日9:52

@CeejeeB已经考虑过。请注意“懒惰”一词,当我关心性能,可伸缩性,维护或可支持性时,我的做法类似于KM。即正确的方法。

– RBarryYoung
2013年6月6日在0:20



@RBarryYoung那是一个很好的创意解决方案,我确实赞成。尽管我从不喜欢看到CharIndex(..)> 0,但我能想到的最语义和可读性最高的替代方法是使用LIKE来了解它是否包含字符串=)干杯!

– T_D
15年1月22日在15:24

原因是在where语句中使用函数会使该语句不可修改,这意味着它将导致扫描。

–汉斯
16年5月26日在10:10

这个穷人的方式正是我所寻找的。我不想创建自定义函数(由于某些原因),我只处理一年中的一组内存天(内存中的365-366条记录),以每年一次填充配置表。太棒了! (是的,我知道这是一个非常老的答案,但还是要谢谢!)

–鸡西店
16 Dec 14'在1:18

#3 楼

没有表没有功能没有循环

基于将列表解析为表的想法,我们的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的答案具有相同的性能,但我认为它要简单得多。

评论


这是其他人告诉我使用的。.能否请您解释SomeTable上的INNER JOIN @ XML.nodes('i')x(i).Id = xivalue('。','VARCHAR(MAX)' )对我一部分?对不起,我对此很陌生。

–阿尔伯特·劳尔
13年7月7日在1:29

@PeterPitLock-是的,请在下面查看我的答案。您可以像使用其他任何表格一样使用xml

– Morvael
16年5月4日在13:49

对我不起作用。在Northwind的Category表中使用CategoryID对其进行了尝试,但得到的错误是:错误493:从node()方法返回的列'i'不能直接使用。它只能与四种XML数据类型方法之一(exist(),nodes(),query()和value())一起使用,或与IS NULL和IS NOT NULL检查一起使用。

–马特
16-09-28在13:25

@Matt我也知道。尝试用SELECT SomeTable。*替换SELECT *,它应该可以工作。

–马特
17年7月24日在0:46

@Matt-我尝试过,但是随后出现另一个错误:错误207:无效的列名“ Id”。

–马特
17年7月24日在8:45

#4 楼

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

所以您的陈述将类似于

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


评论


此循环将很慢,您无需循环即可在SQL中拆分字符串,请参阅我的答案以获取有关如何执行此操作的示例...

–KM。
09年5月18日在17:51

这可能是您希望RDBMS具有一流的数组支持的原因之一fxjr.blogspot.com/2009/05/…将CLR与MSSQL集成以实现IN的多个值,供应商锁定:sommarskog.se/arrays-in -sql-2005.html

–迈克尔·布恩(Michael Buen)
09年5月19日,3:30

#5 楼

这是一个非常普遍的问题。罐头答案,几种不错的技术:

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

评论


链接页面确实具有一些很棒的信息,尤其是如果您想降低CLR路线的话。

–DaveD
13年9月28日在21:37

#6 楼

这样完美!以下答案过于复杂。不要认为这是动态的。按照以下步骤设置存储过程:

(@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);选择``嗨,我刚刚用软管接过您的服务器...''-'

–海滩
09年5月18日在17:41

嗯,打针。但这通常仅在允许用户输入时适用。

–埃里克
09年5月18日在18:23

除了安全性之外,从性能的角度来看,使用串联文字也不是一个好主意:每次使用@id中的不同值执行SQL语句时,串联文字都会在查询计划缓存中创建重复的查询计划。如果这是一台繁忙的服务器,请说“ hola”以查询计划缓存膨胀(参考mssqltips.com/sqlservertip/2681/…)

–杰夫·梅格勒(Jeff Mergler)
17年8月4日在0:30

#7 楼

在不使用动态SQL的情况下,您必须使用输入变量并使用split函数将数据放入临时表中,然后再加入该表中。

#8 楼

我可以建议像这样使用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添加了强制类型转换,以加入表的唯一标识符。

– gknicker
2015年11月24日,0:47

#9 楼

谢谢您的使用,我用过它的功能.........................
这是我的示例

**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


#10 楼

如果使用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


#11 楼

已经有一段时间了,但是我过去使用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子句。使用这个例子有帮助。谢谢!

–罗布·萨德勒
17年5月10日在21:42

#12 楼

创建一个如下所示的表函数,该函数解析逗号分隔的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


#13 楼

我认为可以采用以下非常简单的解决方案:

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)+',%';


评论


你能解释一下吗?

–sf9v
17年7月7日在2:49

显然,类似的运算符用于过滤记录,我通常在这种情况下使用了很长时间。这真的很容易理解。

–user1400290
17年12月8日在7:45

#14 楼

我已经写了一个存储过程来演示如何执行此操作。
您基本上必须处理该字符串。
我试图在此处发布代码,但是格式设置很麻烦。

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


评论


您的答案有一些断开的链接...您可以检查出来吗?

– T. Sar
2015年9月23日下午17:32

根据我的要求添加了代码,尽管我不确定我是否再使用此算法。我切换到传递xml,然后在一段时间前使用sql的xml支持。

–里卡德斯
2015年9月23日19:29在

#15 楼

我对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


#16 楼

尝试以下操作:

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


如该链接的最后一个示例所述

#17 楼

-- 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


#18 楼

您可以这样操作:

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;


您可以在此使用-在其他情况下也可以使用regexp_substr,只需尝试一下即可。

#19 楼

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中通过使用以下代码段修复了

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)')


#20 楼

最佳且简单的方法。

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)


#21 楼

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 


工作小提琴在这里找到小提琴

评论


这与2009年在此处发布的方法相同。

–安德鲁(Andrew)
19年2月8日于13:51

#22 楼

我发现的最简单方法是使用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()

–Vasiliy Zverev
19年12月13日在14:04

#23 楼

我遇到了同样的问题,并且我不想在源数据库上有任何占用空间-即,没有存储过程或函数。我是这样处理的:
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)


#24 楼

这对于我不想使用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_SplitList(@ids, ','))