将一个充满逗号分隔值的varchar传递给SQL Server IN函数

Van*_*ith 53 sql t-sql sql-server sql-in

使用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);
Run Code Online (Sandbox Code Playgroud)

这当然不起作用.我收到错误:

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

如何在不诉诸构建动态SQL的情况下完成此任务(或相对类似的东西)?

RBa*_*ung 56

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

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
Run Code Online (Sandbox Code Playgroud)

  • @CeejeeB已经考虑过了.注意单词"*lazy*",当我关心性能,可扩展性,维护或可支持性时,我会像KM那样做.答案.IE,*对*方式. (11认同)
  • 我使用这种方法,它工作正常,直到我部署到我们的实时服务器,其中有450万行,此时它太慢了.始终考虑可扩展性! (5认同)

KM.*_*KM. 43

不要使用循环分割字符串的函数!,我的下面的函数会快速分割一个字符串,没有循环!

在使用我的函数之前,需要设置一个"帮助程序"表,每个数据库只需执行一次:

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
Run Code Online (Sandbox Code Playgroud)

使用此函数来分割您的字符串,它不会循环并且非常快:

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
Run Code Online (Sandbox Code Playgroud)

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

SELECT
    Col1, COl2, Col3...
    FROM  YourTable
        INNER JOIN FN_ListToTable(',',@YourString) s ON  YourTable.ID = s.ListValue
Run Code Online (Sandbox Code Playgroud)

这是你的例子:

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

  • 当您执行Select语句时,查询程序正在做什么? - 使用跨时间量子物理瞬间生成所有行?它也是循环...你只是从一个你明确控制的循环变为一个SQL Server查询处理器控件... (7认同)
  • 查尔斯和KM。您的每条评论都有一些优点。是的,SQL引擎有时会遍历各个数字。但是引擎的循环可能比用户编写的循环快得多。为了避免首先出现循环,真正的解决方案是重新设计架构以符合第一范式。CSV字段看起来像1NF,但实际上不是1NF。那是真正的问题。 (2认同)

Cee*_*eeB 18

没有表没有功能没有循环

基于将列表解析为表的想法,我们的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)')
Run Code Online (Sandbox Code Playgroud)

这些似乎与@ KM的答案具有相同的性能,但我认为,更简单.

  • @PeterPitLock - 是的,请参阅下面的答案.您可以像使用任何其他表一样使用xml (2认同)
  • @Matt我也知道。尝试用SELECT SomeTable。*替换SELECT *,它应该可以工作。 (2认同)

小智 11

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

所以你的陈述会是这样的

select * from someable 
 join Splitfunction(@ids) as splits on sometable.id = splits.id
Run Code Online (Sandbox Code Playgroud)

这是一个类似的功能.

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
Run Code Online (Sandbox Code Playgroud)


A-K*_*A-K 10

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

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


Eri*_*ric 8

这很完美!以下答案太复杂了.不要把它视为动态.设置您的商店程序如下:

(@id as varchar(50))
as

Declare @query as nvarchar(max)
set @query ='
select * from table
where id in('+@id+')'
EXECUTE sp_executesql @query
Run Code Online (Sandbox Code Playgroud)

  • 不明智....试试这个:SET @id ='0); SELECT''嗨,我只是把你的服务器给了......'' - ' (4认同)
  • 啊,注射.但这通常仅适用于允许用户输入的情况. (4认同)
  • 除了安全性之外,从性能角度来看,使用串联文字也不是一个好主意:每次使用 @id 中的不同值执行 SQL 语句时,串联文字都会在查询计划缓存中创建重复的查询计划。如果这是一台繁忙的服务器,请说“hola”来查询计划缓存膨胀(参考https://www.mssqltips.com/sqlservertip/2681/minimize-sql-server-plan-cache-bloat/) (2认同)

HLG*_*GEM 6

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


shA*_*A.t 5

我可以建议这样使用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;
Run Code Online (Sandbox Code Playgroud)

  • 辉煌。我在CTE ID上向int添加了强制类型转换,以加入到表的唯一标识符中。 (2认同)

use*_*290 5

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

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)+',%';
Run Code Online (Sandbox Code Playgroud)

  • 很明显,like 运算符用于过滤记录,我通常在这种情况下使用它很长一段时间。它非常简单且易于理解。 (2认同)