【问题标题】:Parse comma-separated string to make IN List of strings in the Where clause解析逗号分隔的字符串以在 Where 子句中生成 IN 字符串列表
【发布时间】:2013-07-03 02:52:31
【问题描述】:

我的存储过程接收到一个以逗号分隔的字符串的参数:

DECLARE @Account AS VARCHAR(200)
SET @Account = 'SA,A'

我需要从中做出这样的声明:

WHERE Account IN ('SA', 'A')

这样做的最佳做法是什么?

【问题讨论】:

    标签: sql sql-server sql-server-2008 tsql


    【解决方案1】:

    创建这个函数(sqlserver 2005+)

    CREATE function [dbo].[f_split]
    (
    @param nvarchar(max), 
    @delimiter char(1)
    )
    returns @t table (val nvarchar(max), seq int)
    as
    begin
    set @param += @delimiter
    
    ;with a as
    (
    select cast(1 as bigint) f, charindex(@delimiter, @param) t, 1 seq
    union all
    select t + 1, charindex(@delimiter, @param, t + 1), seq + 1
    from a
    where charindex(@delimiter, @param, t + 1) > 0
    )
    insert @t
    select substring(@param, f, t - f), seq from a
    option (maxrecursion 0)
    return
    end
    

    使用此语句

    SELECT *
    FROM yourtable 
    WHERE account in (SELECT val FROM dbo.f_split(@account, ','))
    

    将我的拆分功能与 XML 拆分进行比较:

    测试数据:

    select top 100000 cast(a.number as varchar(10))+','+a.type +','+ cast(a.status as varchar(9))+','+cast(b.number as varchar(10))+','+b.type +','+ cast(b.status as varchar(9)) txt into a 
    from master..spt_values a cross join master..spt_values b
    

    XML:

     SELECT count(t.c.value('.', 'VARCHAR(20)'))
     FROM (
         SELECT top 100000 x = CAST('<t>' + 
               REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
               from a
     ) a
     CROSS APPLY x.nodes('/t') t(c)
    
    Elapsed time: 1:21 seconds
    

    f_split:

    select count(*) from a cross apply clausens_base.dbo.f_split(a.txt, ',')
    
    Elapsed time: 43 seconds
    

    这会随着运行而变化,但你明白了

    【讨论】:

    • 如果@param为空会发生什么,我认为设置@param += @delimiter应该设置@param =ISNULL(@param,'')+@delimiter
    • @SuryaPratap 简单明了 - 不。这对结果没有影响。
    【解决方案2】:

    试试这个 -

    DDL:

    CREATE TABLE dbo.Table1 (
          [EmpId] INT
        , [FirstName] VARCHAR(7)
        , [LastName] VARCHAR(10)
        , [domain] VARCHAR(6)
        , [Vertical] VARCHAR(10)
        , [Account] VARCHAR(50)
        , [City] VARCHAR(50)
    )
    
    INSERT INTO dbo.Table1 ([EmpId], [FirstName], [LastName], [Vertical], [Account], [domain], [City])
    VALUES 
         (345, 'Priya', 'Palanisamy', 'DotNet', 'LS', 'Abbott', 'Chennai'),
         (346, 'Kavitha', 'Amirtharaj', 'DotNet', 'CG', 'Diageo', 'Chennai'),
         (647, 'Kala', 'Haribabu', 'DotNet', 'DotNet', 'IMS', 'Chennai')
    

    查询:

    DECLARE @Account VARCHAR(200)
    SELECT @Account = 'CG,LS'
    
    SELECT *
    FROM Table1
    WHERE [Vertical] = 'DotNet' AND (ISNULL(@Account, '') = '' OR Account IN (
         SELECT t.c.value('.', 'VARCHAR(20)')
         FROM (
             SELECT x = CAST('<t>' + 
                   REPLACE(@Account, ',', '</t><t>') + '</t>' AS XML)
         ) a
         CROSS APPLY x.nodes('/t') t(c)
    ))
    

    输出:

    扩展统计:

    SSMS 设置统计时间 + IO:

    XML:

    (3720 row(s) affected)
    Table 'temp'. Scan count 3, logical reads 7, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    
     SQL Server Execution Times:
       CPU time = 187 ms,  elapsed time = 242 ms.
    

    CTE:

    (3720 row(s) affected)
    Table '#BF78F425'. Scan count 360, logical reads 360, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'temp'. Scan count 1, logical reads 7, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    
     SQL Server Execution Times:
       CPU time = 281 ms,  elapsed time = 335 ms.
    

    【讨论】:

    • 试试这个:WHERE [Vertical] = @Vertical AND (@Account IS NULL OR Account IN (...))
    • 感谢您的回复我已经尝试过了,但结果应该返回 2 条记录,这里只返回 0 条记录
    • [EmpId]、[FirstName]、[LastName]、[Domain]、[Vertical]、[Account] 345、'Priya'、'Palanisamy'、'DotNet'、'LS'、' Abbott' 346、'Kavitha'、'Amirtharaj'、'DotNet'、'CG'、'Diageo' 647、'Kala'、'Haribabu'、'DotNet'、'DotNet'、'IMS' 我想得到结果“select * from Table where vertical='DotNet' and Account in ('CG','LS')”这个查询使用存储过程(通过动态传递垂直,帐户值)
    • 变量@Vertical@Account中包含什么?
    • @Devart 结果包含在我的答案中。顺便提一句。我的回答也比 Alexanders 链接中的 CTE 略​​快
    【解决方案3】:

    最有效的方法是使用 CLR 函数来分割字符串。示例和性能比较见this article

    【讨论】:

    • 那篇文章中描述的 CTE 函数确实很弱(与我描述的相比)。他选择了 CTE 中的所有值,而不是查找分隔符的位置。这是以比必要更多的计算机资源为代价的,并且没有真正给 CTE 一个公平的机会。
    【解决方案4】:

    SQL server 2016+ 提供了此处描述的 String_Split:https://www.sqlshack.com/the-string-split-function-in-sql-server/,它非常适合我的目的并且易于使用,例如:

    declare @InClauesQueryString nvarchar(100) = '1,2'; 
    declare @exampleTable table(ID int,Val nvarchar(100));
    INSERT INTO @exampleTable (ID, Val) VALUES (1,'test1');
    INSERT INTO @exampleTable (ID, Val) VALUES (2,'test2');
    select * from @exampleTable where id in (select * from string_split(@InClauesQueryString,','));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 2011-02-20
      • 2021-11-01
      • 2012-07-16
      • 2011-04-06
      • 1970-01-01
      相关资源
      最近更新 更多