【问题标题】:SQL Server - In clause with a declared variable [duplicate]SQL Server - 带有声明变量的In子句[重复]
【发布时间】:2011-02-26 00:50:14
【问题描述】:

假设我得到了以下内容:

DECLARE @ExcludedList VARCHAR(MAX)

SET @ExcludedList = 3 + ', ' + 4 + ' ,' + '22'

SELECT * FROM A WHERE Id NOT IN (@ExcludedList)

错误:将 varchar 值 ', ' 转换为数据类型 int 时转换失败。

我明白为什么会出现错误,但我不知道如何解决它......

【问题讨论】:

  • @TomTom 等人 - 我不同意这是重复的。另一个问题涵盖了更多与该问题具体解决的问题无关的基础。最重要的是,我很高兴我找到了这篇文章而不是另一篇 - 因为这篇文章准确地解决了我的问题。
  • 也许你可以使用string_split,它可以在SQL Server 2016找到,把你的代码改成select * from A where Id not In (select value from string_split('3;4;5;6',';'))

标签: sql sql-server-2008 in-clause


【解决方案1】:

这是一个示例,我使用 table 变量列出多个 IN 子句中的值。显而易见的原因是能够改变 在很长的过程中,值列表只有一个位置。

为了使它更加动态和允许用户输入,我建议 为输入声明一个 varchar 变量,然后使用 WHILE 循环遍历变量中的数据并将其插入表中 变量。

用真实的东西替换@your_list、Your_table 和值。

DECLARE @your_list TABLE (list varchar(25)) 
INSERT into @your_list
VALUES ('value1'),('value2376')

SELECT *  
FROM your_table 
WHERE your_column in ( select list from @your_list )

上面的选择语句将执行相同的操作:

SELECT *  
FROM your_table 
WHERE your_column in ('value','value2376' )

【讨论】:

  • 这将导致性能下降,因为 in 子句中的第二个选择,因为它将为每一行执行
  • 我使用的是同样的东西,但问题是如果我在“IN”子句中使用 table,大约需要 20 秒。但是,如果我在那里使用一个字符串,那么它的运行时间少于 2。所以我想避免使用临时表并为 IN 子句创建一个字符串。
【解决方案2】:

你需要像动态sp一样执行这个

DECLARE @ExcludedList VARCHAR(MAX)

SET @ExcludedList = '3,4,22,6014'
declare @sql nvarchar(Max)

Set @sql='SELECT * FROM [A] WHERE Id NOT IN ('+@ExcludedList+')'

exec sp_executesql @sql

【讨论】:

  • 如果用户输入了 ExcludedList,这是否容易受到 SQL 注入的影响?例如,ExcludedList = '3);删除表用户; --'(评论系统不允许我使用 (at) 符号)
  • 没关系。多个变量(每个项目一个)或动态 SQL 是处理此问题的唯一方法。是的,必须小心。或删除 in 子句(并将项目加载到临时表/表变量中,然后加入)。
  • 我更喜欢使用字符串拆分器而不是动态 sql 以避免 sql 注入。 sqlperformance.com/2012/07/t-sql-queries/split-strings
  • 这是有效的,但不是唯一的答案。所有需要审查的答案都有利有弊:)
  • 我们应该使用动态 sql 还是应该将 csv 字符串解析并存储到临时表中,然后使用主表连接。我知道后者有更多的步骤,但只是想检查哪种方式会更有效地考虑性能。
【解决方案3】:
DECLARE @IDQuery VARCHAR(MAX)
SET @IDQuery = 'SELECT ID FROM SomeTable WHERE Condition=Something'
DECLARE @ExcludedList TABLE(ID VARCHAR(MAX))
INSERT INTO @ExcludedList EXEC(@IDQuery)    
SELECT * FROM A WHERE Id NOT IN (@ExcludedList)

我知道我正在回复一篇旧帖子,但我想分享一个示例,说明如何在想要避免使用动态 SQL 时使用变量表。我不确定它是否是最有效的方法,但是在过去,当动态 SQL 不是一个选项时,这对我来说是有效的。

【讨论】:

  • 我喜欢这个,因为你避免使用动态 SQL
  • 我认为EXEC(@IDQuery) 是动态的?
  • 不过,您没有必须这样做。我不太确定他为什么这样做。你可以这样做。 DECLARE @SomeTableVar TABLE(id INT) INSERT INTO @SomeTableVar SELECT some_int FROM some_table WHERE some_int IS NOT NULL SELECT * FROM @SomeTableVar 我认为这只是一个人为的例子。顺便说一句,对不起,任何评论回复垃圾邮件,伙计们。我是一个大傻瓜,不明白评论降价与 StackOverflow 上的答案降价不同。 :v
  • IN(@variable) 需要一个标量,而不是一个表变量
【解决方案4】:

您不能在 IN 子句中使用变量 - 您需要使用 dynamic SQL,或使用 a function (TSQL or CLR) to convert the list of values into a table

动态 SQL 示例:

DECLARE @ExcludedList VARCHAR(MAX)
    SET @ExcludedList = 3 + ',' + 4 + ',' + '22'

DECLARE @SQL NVARCHAR(4000)
    SET @SQL = 'SELECT * FROM A WHERE Id NOT IN (@ExcludedList) '

 BEGIN

   EXEC sp_executesql @SQL '@ExcludedList VARCHAR(MAX)' @ExcludedList

 END

【讨论】:

    【解决方案5】:

    首先,创建一个快速函数,将分隔的值列表拆分为表格,如下所示:

    CREATE FUNCTION dbo.udf_SplitVariable
    (
        @List varchar(8000),
        @SplitOn varchar(5) = ','
    )
    
    RETURNS @RtnValue TABLE
    (
        Id INT IDENTITY(1,1),
        Value VARCHAR(8000)
    )
    
    AS
    BEGIN
    
    --Account for ticks
    SET @List = (REPLACE(@List, '''', ''))
    
    --Account for 'emptynull'
    IF LTRIM(RTRIM(@List)) = 'emptynull'
    BEGIN
        SET @List = ''
    END
    
    --Loop through all of the items in the string and add records for each item
    WHILE (CHARINDEX(@SplitOn,@List)>0)
    BEGIN
    
        INSERT INTO @RtnValue (value)
        SELECT Value = LTRIM(RTRIM(SUBSTRING(@List, 1, CHARINDEX(@SplitOn, @List)-1)))  
    
        SET @List = SUBSTRING(@List, CHARINDEX(@SplitOn,@List) + LEN(@SplitOn), LEN(@List))
    
    END
    
    INSERT INTO @RtnValue (Value)
    SELECT Value = LTRIM(RTRIM(@List))
    
    RETURN
    
    END 
    

    然后像这样调用函数...

    SELECT * 
    FROM A
    LEFT OUTER JOIN udf_SplitVariable(@ExcludedList, ',') f ON A.Id = f.Value
    WHERE f.Id IS NULL
    

    这对我们的项目非常有效......

    当然,如果是这样的话,也可以做相反的事情(尽管不是你的问题)。

    SELECT * 
    FROM A
    INNER JOIN udf_SplitVariable(@ExcludedList, ',') f ON A.Id = f.Value
    

    在处理具有可选多选参数列表的报表时,这真的很方便。如果参数为 NULL,您希望选择所有值,但如果它有一个或多个值,您希望报表数据根据这些值进行过滤。然后像这样使用 SQL:

    SELECT * 
    FROM A
    INNER JOIN udf_SplitVariable(@ExcludedList, ',') f ON A.Id = f.Value OR @ExcludeList IS NULL
    

    这样,如果@ExcludeList 是一个NULL 值,则连接中的OR 子句变成一个开关,关闭对该值的过滤。很方便...

    【讨论】:

    • 这是一个极好的解决方案。它应该是公认的答案。它也适用于 varchar 项,这在动态 SQL 答案中变得很麻烦。
    【解决方案6】:

    我认为问题出在

    3 + ', ' + 4
    

    改成

    '3' + ', ' + '4'
    
    DECLARE @ExcludedList VARCHAR(MAX)
    
    SET @ExcludedList = '3' + ', ' + '4' + ' ,' + '22'
    
    SELECT * FROM A WHERE Id NOT IN (@ExcludedList)
    

    SET @ExcludedListe 使您的查询变为

    任一

    SELECT * FROM A WHERE Id NOT IN ('3', '4', '22')
    

    SELECT * FROM A WHERE Id NOT IN (3, 4, 22)
    

    【讨论】:

    • 修复了 set 语句,但 select 查询仍然不起作用
    • 这对我很有用
    【解决方案7】:

    试试这个:

    CREATE PROCEDURE MyProc @excludedlist integer_list_tbltype READONLY AS
      SELECT * FROM A WHERE ID NOT IN (@excludedlist)
    

    然后这样称呼它:

    DECLARE @ExcludedList integer_list_tbltype
    INSERT @ExcludedList(n) VALUES(3, 4, 22)
    exec MyProc @ExcludedList
    

    【讨论】:

      【解决方案8】:

      我有另一种解决方案,无需动态查询。 我们也可以借助 xquery 来做到这一点。

          SET @Xml = cast(('<A>'+replace('3,4,22,6014',',' ,'</A><A>')+'</A>') AS XML)
          Select @Xml
      
          SELECT A.value('.', 'varchar(max)') as [Column] FROM @Xml.nodes('A') AS FN(A)
      

      这是完整的解决方案: http://raresql.com/2011/12/21/how-to-use-multiple-values-for-in-clause-using-same-parameter-sql-server/

      【讨论】:

        猜你喜欢
        • 2021-12-29
        • 2021-04-17
        • 2013-06-25
        • 1970-01-01
        • 1970-01-01
        • 2012-03-13
        • 2012-08-25
        • 1970-01-01
        相关资源
        最近更新 更多