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子句[重复]的主要内容,如果未能解决你的问题,请参考以下文章