SQL Server - 带有声明变量的In子句[重复]

Posted

技术标签:

【中文标题】SQL Server - 带有声明变量的In子句[重复]【英文标题】:SQL Server - In clause with a declared variable [duplicate] 【发布时间】: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',';')) 【参考方案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 我认为这只是一个人为的例子。顺便说一句,对不起,任何评论回复垃圾邮件,伙计们。我是一个大傻瓜,不明白评论降价与 *** 上的答案降价不同。 :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/

【讨论】:

以上是关于SQL Server - 带有声明变量的In子句[重复]的主要内容,如果未能解决你的问题,请参考以下文章

带有 IN 子句参数的 Oracle 存储过程

在SQL Server存储过程中声明变量列表

使用带有休眠 SQL 查询的多列的参数化 IN 子句

在 WHERE 子句中为 IN 条件声明变量

IN vs = in Where 子句

如何在 NOT IN 子句中使用字符串作为变量?