【问题标题】:Define variable to use with IN operator (T-SQL)定义要与 IN 运算符一起使用的变量 (T-SQL)
【发布时间】:2010-12-15 00:09:30
【问题描述】:

我有一个使用 IN 运算符的 Transact-SQL 查询。像这样的:

select * from myTable where myColumn in (1,2,3,4)

有没有办法定义一个变量来保存整个列表“(1,2,3,4)”?我该如何定义呢?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList

【问题讨论】:

  • 这个问题与“Parameterize an SQL IN clause”问题不同。本题指的是原生T-SQL,另一题指的是C#。
  • @SlogmeisterExtraordinaire 提到的问题是这个:stackoverflow.com/questions/337704/…

标签: sql tsql


【解决方案1】:
DECLARE @MyList TABLE (Value INT)
INSERT INTO @MyList VALUES (1)
INSERT INTO @MyList VALUES (2)
INSERT INTO @MyList VALUES (3)
INSERT INTO @MyList VALUES (4)

SELECT *
FROM MyTable
WHERE MyColumn IN (SELECT Value FROM @MyList)

【讨论】:

    【解决方案2】:
    DECLARE @mylist TABLE (Id int)
    INSERT INTO @mylist
    SELECT id FROM (VALUES (1),(2),(3),(4),(5)) AS tbl(id)
    
    SELECT * FROM Mytable WHERE theColumn IN (select id from @mylist)
    

    【讨论】:

    • 可以直接用(VALUES (1),(2),(3),(4),(5))吗?
    • 这是满足我需求的最佳解决方案。我需要一个变量作为我从 Select 获得的 Id 列表,因此这些值不是预先确定的。这正是我所需要的。谢谢!
    【解决方案3】:

    有两种方法可以处理 TSQL 查询的动态 csv 列表:

    1) 使用内部选择

    SELECT * FROM myTable WHERE myColumn in (SELECT id FROM myIdTable WHERE id > 10)
    

    2) 使用动态连接的 TSQL

    DECLARE @sql varchar(max)  
    declare @list varchar(256)  
    select @list = '1,2,3'  
    SELECT @sql = 'SELECT * FROM myTable WHERE myColumn in (' + @list + ')'
    
    exec sp_executeSQL @sql
    

    3) 第三个可能的选项是表变量。如果您有 SQl Server 2005,您可以使用表变量。如果您在 Sql Server 2008 上,您甚至可以将整个表变量作为参数传递给存储过程,并将其用于连接或作为 IN 子句中的子选择。

    DECLARE @list TABLE (Id INT)
    
    INSERT INTO @list(Id)
    SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
    
    
    SELECT
        * 
    FROM 
        myTable
        JOIN @list l ON myTable.myColumn = l.Id
    
    SELECT
        * 
    FROM 
        myTable
    WHERE
        myColumn IN (SELECT Id FROM @list)
    

    【讨论】:

    • @badbod99 - 这是一个概括,所有概括都是错误的 :) 我提供了替代方案
    • @Vilx - 你的意思是设置变量@list 吗?如果这样设置很好,但只设置一个变量,使用 select 您可以在一个语句中填充多个变量。因为它们之间没有太多关系,所以我习惯于总是使用 SELECT。
    • 是的……非常笼统。你的替代方案更好。我的意思是从 SQL 脚本中生成 SQL 通常会导致无法维护的代码、注入攻击的风险和大量其他问题。
    【解决方案4】:

    使用这样的函数:

    CREATE function [dbo].[list_to_table] (@list varchar(4000))
    returns @tab table (item varchar(100))
    begin
    
    if CHARINDEX(',',@list) = 0 or CHARINDEX(',',@list) is null
    begin
        insert into @tab (item) values (@list);
        return;
    end
    
    
    declare @c_pos int;
    declare @n_pos int;
    declare @l_pos int;
    
    set @c_pos = 0;
    set @n_pos = CHARINDEX(',',@list,@c_pos);
    
    while @n_pos > 0
    begin
        insert into @tab (item) values (SUBSTRING(@list,@c_pos+1,@n_pos - @c_pos-1));
        set @c_pos = @n_pos;
        set @l_pos = @n_pos;
        set @n_pos = CHARINDEX(',',@list,@c_pos+1);
    end;
    
    insert into @tab (item) values (SUBSTRING(@list,@l_pos+1,4000));
    
    return;
    end;
    

    您可以与函数返回的表进行内连接,而不是使用like:

    select * from table_1 where id in ('a','b','c')
    

    变成

    select * from table_1 a inner join [dbo].[list_to_table] ('a,b,c') b on (a.id = b.item)
    

    在一个未索引的 1M 记录表中,第二个版本花费了大约一半的时间......

    【讨论】:

      【解决方案5】:

      从 SQL2017 开始,您可以使用 STRING_SPLIT 并执行以下操作:

      declare @myList nvarchar(MAX)
      set @myList = '1,2,3,4'
      select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))
      

      【讨论】:

      • 这似乎重复了 Nathan Evans 一个月前的解决方案。
      • @TylerH 同意,显然我没有注意到。
      【解决方案6】:
      DECLARE @myList TABLE (Id BIGINT) INSERT INTO @myList(Id) VALUES (1),(2),(3),(4);
      select * from myTable where myColumn in(select Id from @myList)
      

      请注意,对于长列表或生产系统,不建议使用这种方式,因为它可能比简单的 INoperator 像 someColumnName in (1,2,3,4) 慢得多(使用 8000 多个项目列表进行测试)

      【讨论】:

        【解决方案7】:

        @LukeH 略有改进,无需重复“INSERT INTO”: 和@realPT 的回答 - 不需要选择:

        DECLARE @MyList TABLE (Value INT) 
        INSERT INTO @MyList VALUES (1),(2),(3),(4)
        
        SELECT * FROM MyTable
        WHERE MyColumn IN (SELECT Value FROM @MyList)
        

        【讨论】:

          【解决方案8】:

          我知道这是旧的,但 TSQL => 2016,你可以使用 STRING_SPLIT:

          DECLARE @InList varchar(255) = 'This;Is;My;List';
          
          WITH InList (Item) AS (
              SELECT value FROM STRING_SPLIT(@InList, ';')
          )
          
          SELECT * 
          FROM [Table]
          WHERE [Item] IN (SELECT Tag FROM InList)
          

          【讨论】:

            【解决方案9】:

            不,没有这种类型。但也有一些选择:

            • 动态生成的查询 (sp_executesql)
            • 临时表
            • 表型变量(最接近列表的东西)
            • 创建一个 XML 字符串,然后使用 XML 函数将其转换为表格(真的很尴尬和迂回,除非你有一个 XML 开头)

            这些都不是真正优雅的,但这是最好的。

            【讨论】:

              【解决方案10】:

              如果您想在不使用第二个表的情况下执行此操作,可以使用 CAST 进行 LIKE 比较:

              DECLARE @myList varchar(15)
              SET @myList = ',1,2,3,4,'
              
              SELECT *
              FROM myTable
              WHERE @myList LIKE '%,' + CAST(myColumn AS varchar(15)) + ',%'
              

              如果您要比较的字段已经是一个字符串,那么您不需要 CAST。

              将列匹配和逗号中的每个唯一值括起来将确保完全匹配。否则,值 1 将在包含 ',4,2,15,' 的列表中找到

              【讨论】:

                【解决方案11】:

                之前没有人提到过,从 Sql Server 2016 开始你还可以使用 json 数组和OPENJSON (Transact-SQL)

                declare @filter nvarchar(max) = '[1,2]'
                
                select *
                from dbo.Test as t
                where
                    exists (select * from openjson(@filter) as tt where tt.[value] = t.id)
                

                您可以在 sql fiddle demo

                您还可以更轻松地使用 json 处理更复杂的情况 - 请参阅 Search list of values and range in SQL using WHERE IN clause with SQL variable?

                【讨论】:

                  【解决方案12】:

                  这个使用 PATINDEX 将表中的 id 匹配到非数字分隔的整数列表。

                  -- Given a string @myList containing character delimited integers 
                  -- (supports any non digit delimiter)
                  DECLARE @myList VARCHAR(MAX) = '1,2,3,4,42'
                  
                  SELECT * FROM [MyTable]
                      WHERE 
                          -- When the Id is at the leftmost position 
                          -- (nothing to its left and anything to its right after a non digit char) 
                          PATINDEX(CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0 
                          OR
                          -- When the Id is at the rightmost position
                          -- (anything to its left before a non digit char and nothing to its right) 
                          PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR), @myList)>0
                          OR
                          -- When the Id is between two delimiters 
                          -- (anything to its left and right after two non digit chars)
                          PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0
                          OR
                          -- When the Id is equal to the list
                          -- (if there is only one Id in the list)
                          CAST([Id] AS VARCHAR)=@myList
                  

                  注意事项:

                  • 当转换为 varchar 且未在括号中指定字节大小时,默认长度为 30
                  • %(通配符)将匹配任何零个或多个字符的字符串
                  • ^(通配符)不匹配
                  • [^0-9] 将匹配任何非数字字符
                  • PATINDEX 是一个 SQL 标准函数,用于返回模式在字符串中的位置

                  【讨论】:

                    【解决方案13】:
                    DECLARE @StatusList varchar(MAX);
                    SET @StatusList='1,2,3,4';
                    DECLARE @Status SYS_INTEGERS;
                    INSERT INTO  @Status 
                    SELECT Value 
                    FROM dbo.SYS_SPLITTOINTEGERS_FN(@StatusList, ',');
                    SELECT Value From @Status;
                    

                    【讨论】:

                    • 如果您在那里描述您的代码,这将是一个更好的答案!
                    【解决方案14】:

                    其中大多数似乎专注于将每个 INT 分离到自己的括号中,例如:

                    • (1)、(2)、(3) 等等……

                    这并不总是很方便。尤其是因为很多时候,您已经以逗号分隔的列表开头,例如:

                    • (1,2,3,...) 等等...

                    在这些情况下,您可能更愿意做这样的事情:

                    DECLARE @ListOfIds TABLE (DocumentId INT);
                    
                    INSERT INTO @ListOfIds
                    SELECT Id FROM [dbo].[Document] WHERE Id IN (206,235,255,257,267,365)
                    
                    SELECT * FROM @ListOfIds
                    

                    我喜欢这种方法,因为我经常尝试使用应该已经存在于表中的 ID。

                    【讨论】:

                      【解决方案15】:

                      我认为您必须声明一个字符串,然后执行该 SQL 字符串。

                      看看sp_executeSQL

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2017-04-01
                        • 1970-01-01
                        • 2013-09-10
                        • 1970-01-01
                        • 2019-05-29
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-10-06
                        相关资源
                        最近更新 更多