将包含逗号分隔值的varchar传递给SQL Server IN函数
重复的
dynamicSQL逗号分隔值查询
使用Like和In参数化查询
我有一个SQL Server存储过程,我想通过一个varchar
充满逗号分隔值到IN
函数。 例如:
DECLARE @Ids varchar(50); SET @Ids = '1,2,3,5,4,6,7,98,234'; SELECT * FROM sometable WHERE tableid IN (@Ids);
这当然不行。 我得到的错误:
将varchar值“1,2,3,5,4,6,7,98,234”转换为数据typesint时转换失败。
我怎样才能做到这一点(或类似的东西),而不诉诸build设dynamicSQL?
不要使用循环来分割string的函数! ,我的下面的函数将非常快地分割一个string,没有循环!
在你使用我的函数之前,你需要设置一个“助手”表,每个数据库只需要这样做一次:
CREATE TABLE Numbers (Number int NOT NULL, CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] DECLARE @x int SET @x=0 WHILE @x<8000 BEGIN SET @x=@x+1 INSERT INTO Numbers VALUES (@x) END
使用这个函数来分割你的string,它不循环,速度非常快:
CREATE FUNCTION [dbo].[FN_ListToTable] ( @SplitOn char(1) --REQUIRED, the character to split the @List string on ,@List varchar(8000) --REQUIRED, the list to split apart ) RETURNS @ParsedList table ( ListValue varchar(500) ) AS BEGIN /** Takes the given @List string and splits it apart based on the given @SplitOn character. A table is returned, one row per split item, with a column name "ListValue". This function workes for fixed or variable lenght items. Empty and null items will not be included in the results set. Returns a table, one row per item in the list, with a column name "ListValue" EXAMPLE: ---------- SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,
,,,,B') returns: ListValue ———– 1 12 123 1234 54321 6 A *
B (10 row(s) affected) **/ —————- –SINGLE QUERY– –this will not return empty rows —————- INSERT INTO @ParsedList (ListValue) SELECT ListValue FROM (SELECT LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number – 1))) AS ListValue FROM ( SELECT @SplitOn + @List + @SplitOn AS List2 ) AS dt INNER JOIN Numbers n ON n.Number < LEN(dt.List2) WHERE SUBSTRING(List2, number, 1) = @SplitOn ) dt2 WHERE ListValue IS NOT NULL AND ListValue!='' RETURN END –Function FN_ListToTable
你可以使用这个函数作为连接中的一个表:
SELECT Col1, COl2, Col3... FROM YourTable INNER JOIN FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue
这是你的例子:
Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable(',',@Ids) s)
当然,如果你像我一样懒,你可以这样做:
Declare @Ids varchar(50) Set @Ids = ',1,2,3,5,4,6,7,98,234,' Select * from sometable where Charindex(','+cast(tableid as varchar(8000))+',', @Ids) > 0
否表否function否循环
基于将列表parsing为表的想法,我们的DBAbuild议使用XML。
Declare @Ids varchar(50) Set @Ids = '1,2,3,5,4,6,7,98,234' DECLARE @XML XML SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML) SELECT * FROM SomeTable INNER JOIN @XML.nodes('i') x(i) ON SomeTable .Id = xivalue('.', 'VARCHAR(MAX)')
这些似乎与@ KM的答案有相同的performance,但是,我认为,要简单得多。
您可以创build一个返回表的函数。
所以你的陈述会是这样的
select * from someable join Splitfunction(@ids) as splits on sometable.id = splits.id
这是一个相似的function。
CREATE FUNCTION [dbo].[FUNC_SplitOrderIDs] ( @OrderList varchar(500) ) RETURNS @ParsedList table ( OrderID int ) AS BEGIN DECLARE @OrderID varchar(10), @Pos int SET @OrderList = LTRIM(RTRIM(@OrderList))+ ',' SET @Pos = CHARINDEX(',', @OrderList, 1) IF REPLACE(@OrderList, ',', '') <> '' BEGIN WHILE @Pos > 0 BEGIN SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1))) IF @OrderID <> '' BEGIN INSERT INTO @ParsedList (OrderID) VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion END SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos) SET @Pos = CHARINDEX(',', @OrderList, 1) END END RETURN END
这是一个非常普遍的问题。 jar头答案,几个不错的技巧:
这完美的作品! 下面的答案太复杂了。 不要把这看成是dynamic的。 设置你的商店程序如下:
(@id as varchar(50)) as Declare @query as nvarchar(max) set @query =' select * from table where id in('+@id+')' EXECUTE sp_executesql @query
在不使用dynamicSQL的情况下,您必须获取inputvariables并使用split函数将数据放入临时表中,然后join到该表中。
我可以build议像这样使用WITH
:
DECLARE @Delim char(1) = ','; SET @Ids = @Ids + @Delim; WITH CTE(i, ls, id) AS ( SELECT 1, CHARINDEX(@Delim, @Ids, 1), SUBSTRING(@Ids, 1, CHARINDEX(@Delim, @Ids, 1) - 1) UNION ALL SELECT i + 1, CHARINDEX(@Delim, @Ids, ls + 1), SUBSTRING(@Ids, ls + 1, CHARINDEX(@Delim, @Ids, ls + 1) - CHARINDEX(@Delim, @Ids, ls) - 1) FROM CTE WHERE CHARINDEX(@Delim, @Ids, ls + 1) > 1 ) SELECT t.* FROM yourTable t INNER JOIN CTE c ON t.id = c.id;
创build一个像下面这样的表函数,它分析逗号分隔的varchar,并返回一个可以与其他表进行内联的表。
CREATE FUNCTION [dbo].[fn_SplitList] ( @inString varchar(MAX) = '', @inDelimiter char(1) = ',' -- Keep the delimiter to 100 chars or less. Generally a delimiter will be 1-2 chars only. ) RETURNS @tbl_Return table ( Unit varchar(1000) COLLATE Latin1_General_BIN ) AS BEGIN INSERT INTO @tbl_Return SELECT DISTINCT LTRIM(RTRIM(piece.value('./text()[1]', 'varchar(1000)'))) COLLATE DATABASE_DEFAULT AS Unit FROM ( -- -- Replace any delimiters in the string with the "X" tag. -- SELECT CAST(('<X>' + REPLACE(s0.prsString, s0.prsSplitDelimit, '</X><X>') + '</X>') AS xml).query('.') AS units FROM ( -- -- Convert the string and delimiter into XML. -- SELECT (SELECT @inString FOR XML PATH('')) AS prsString, (SELECT @inDelimiter FOR XML PATH('')) AS prsSplitDelimit ) AS s0 ) AS s1 CROSS APPLY units.nodes('X') x(piece) RETURN END
=================================================现在在代码中消耗上面创build的表函数,函数的创build是数据库中的一次性活动,可以跨数据库在同一台服务器上使用。
DECLARE @Ids varchar(50); SET @Ids = '1,2,3,5,4,6,7,98,234'; SELECT * FROM sometable AS st INNER JOIN fn_SplitList(@ids, ',') AS sl ON sl.unit = st.tableid
谢谢,你的function我用IT ……………………这是我的例子
**UPDATE [RD].[PurchaseOrderHeader] SET [DispatchCycleNumber] ='10' WHERE OrderNumber in(select * FROM XA.fn_SplitOrderIDs(@InvoiceNumberList))** CREATE FUNCTION [XA].[fn_SplitOrderIDs] ( @OrderList varchar(500) ) RETURNS @ParsedList table ( OrderID int ) AS BEGIN DECLARE @OrderID varchar(10), @Pos int SET @OrderList = LTRIM(RTRIM(@OrderList))+ ',' SET @Pos = CHARINDEX(',', @OrderList, 1) IF REPLACE(@OrderList, ',', '') <> '' BEGIN WHILE @Pos > 0 BEGIN SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1))) IF @OrderID <> '' BEGIN INSERT INTO @ParsedList (OrderID) VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion END SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos) SET @Pos = CHARINDEX(',', @OrderList, 1) END END RETURN END
如果使用SQL Server 2008或更高版本,请使用表值参数; 例如:
CREATE PROCEDURE [dbo].[GetAccounts](@accountIds nvarchar) AS BEGIN SELECT * FROM accountsTable WHERE accountId IN (select * from @accountIds) END CREATE TYPE intListTableType AS TABLE (n int NOT NULL) DECLARE @tvp intListTableType -- inserts each id to one row in the tvp table INSERT @tvp(n) VALUES (16509),(16685),(46173),(42925),(46167),(5511) EXEC GetAccounts @tvp
我认为一个非常简单的解决scheme可以是:
DECLARE @Ids varchar(50); SET @Ids = '1,2,3,5,4,6,7,98,234'; SELECT * FROM sometable WHERE ','+@Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';
我写了一个存储过程来显示如何做到这一点。 你基本上必须处理string。 我试图在这里发布的代码,但格式得到了所有螺丝。
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[uspSplitTextList]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1) DROP PROCEDURE [dbo].[uspSplitTextList] GO SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON GO /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -- uspSplitTextList -- -- Description: -- splits a separated list of text items and returns the text items -- -- Arguments: -- @list_text - list of text items -- @Delimiter - delimiter -- -- Notes: -- 02/22/2006 - WSR : use DATALENGTH instead of LEN throughout because LEN doesn't count trailing blanks -- -- History: -- 02/22/2006 - WSR : revised algorithm to account for items crossing 8000 character boundary -- 09/18/2006 - WSR : added to this project -- CREATE PROCEDURE uspSplitTextList @list_text text, @Delimiter varchar(3) AS SET NOCOUNT ON DECLARE @InputLen integer -- input text length DECLARE @TextPos integer -- current position within input text DECLARE @Chunk varchar(8000) -- chunk within input text DECLARE @ChunkPos integer -- current position within chunk DECLARE @DelimPos integer -- position of delimiter DECLARE @ChunkLen integer -- chunk length DECLARE @DelimLen integer -- delimiter length DECLARE @ItemBegPos integer -- item starting position in text DECLARE @ItemOrder integer -- item order in list DECLARE @DelimChar varchar(1) -- first character of delimiter (simple delimiter) -- create table to hold list items -- actually their positions because we may want to scrub this list eliminating bad entries before substring is applied CREATE TABLE #list_items ( item_order integer, item_begpos integer, item_endpos integer ) -- process list IF @list_text IS NOT NULL BEGIN -- initialize SET @InputLen = DATALENGTH(@list_text) SET @TextPos = 1 SET @DelimChar = SUBSTRING(@Delimiter, 1, 1) SET @DelimLen = DATALENGTH(@Delimiter) SET @ItemBegPos = 1 SET @ItemOrder = 1 SET @ChunkLen = 1 -- cycle through input processing chunks WHILE @TextPos <= @InputLen AND @ChunkLen <> 0 BEGIN -- get current chunk SET @Chunk = SUBSTRING(@list_text, @TextPos, 8000) -- setup initial variable values SET @ChunkPos = 1 SET @ChunkLen = DATALENGTH(@Chunk) SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos) -- loop over the chunk, until the last delimiter WHILE @ChunkPos <= @ChunkLen AND @DelimPos <> 0 BEGIN -- see if this is a full delimiter IF SUBSTRING(@list_text, (@TextPos + @DelimPos - 1), @DelimLen) = @Delimiter BEGIN -- insert position INSERT INTO #list_items (item_order, item_begpos, item_endpos) VALUES (@ItemOrder, @ItemBegPos, (@TextPos + @DelimPos - 1) - 1) -- adjust positions SET @ItemOrder = @ItemOrder + 1 SET @ItemBegPos = (@TextPos + @DelimPos - 1) + @DelimLen SET @ChunkPos = @DelimPos + @DelimLen END ELSE BEGIN -- adjust positions SET @ChunkPos = @DelimPos + 1 END -- find next delimiter SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos) END -- adjust positions SET @TextPos = @TextPos + @ChunkLen END -- handle last item IF @ItemBegPos <= @InputLen BEGIN -- insert position INSERT INTO #list_items (item_order, item_begpos, item_endpos) VALUES (@ItemOrder, @ItemBegPos, @InputLen) END -- delete the bad items DELETE FROM #list_items WHERE item_endpos < item_begpos -- return list items SELECT SUBSTRING(@list_text, item_begpos, (item_endpos - item_begpos + 1)) AS item_text, item_order, item_begpos, item_endpos FROM #list_items ORDER BY item_order END DROP TABLE #list_items RETURN /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ GO SET QUOTED_IDENTIFIER OFF GO SET ANSI_NULLS ON GO
它已经有一段时间了,但过去我曾经使用XML作为过渡。
我不能相信这一点,但恐怕我不知道从哪里得到这个想法:
-- declare the variables needed DECLARE @xml as xml,@str as varchar(100),@delimiter as varchar(10) -- The string you want to split SET @str='A,B,C,D,E,Bert,Ernie,1,2,3,4,5' -- What you want to split on. Can be a single character or a string SET @delimiter =',' -- Convert it to an XML document SET @xml = cast(('<X>'+replace(@str,@delimiter ,'</X><X>')+'</X>') as xml) -- Select back from the XML SELECT N.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as T(N)
我对用户KM有同样的想法。 但不需要额外的表号。 只是这个function而已。
CREATE FUNCTION [dbo].[FN_ListToTable] ( @SplitOn char(1) --REQUIRED, the character to split the @List string on ,@List varchar(8000) --REQUIRED, the list to split apart ) RETURNS @ParsedList table ( ListValue varchar(500) ) AS BEGIN DECLARE @number int = 0 DECLARE @childString varchar(502) = '' DECLARE @lengthChildString int = 0 DECLARE @processString varchar(502) = @SplitOn + @List + @SplitOn WHILE @number < LEN(@processString) BEGIN SET @number = @number + 1 SET @lengthChildString = CHARINDEX(@SplitOn, @processString, @number + 1) - @number - 1 IF @lengthChildString > 0 BEGIN SET @childString = LTRIM(RTRIM(SUBSTRING(@processString, @number + 1, @lengthChildString))) IF @childString IS NOT NULL AND @childString != '' BEGIN INSERT INTO @ParsedList(ListValue) VALUES (@childString) SET @number = @number + @lengthChildString - 1 END END END RETURN END
这是testing:
SELECT ListValue FROM dbo.FN_ListToTable('/','a/////bb/c')
结果:
ListValue ______________________ a bb c
-- select * from dbo.Split_ID('77,106') ALTER FUNCTION dbo.Split_ID(@String varchar(8000)) returns @temptable TABLE (ID varchar(8000)) as begin declare @idx int declare @slice varchar(8000) declare @Delimiter char(1) set @Delimiter =',' select @idx = 1 if len(@String)<1 or @String is null return while @idx!= 0 begin set @idx = charindex(@Delimiter,@String) if @idx!=0 set @slice = left(@String,@idx - 1) else set @slice = @String if(len(@slice)>0) insert into @temptable(ID) values(@slice) set @String = right(@String,len(@String) - @idx) if len(@String) = 0 break end return end
你可以这样做:
create or replace PROCEDURE UDP_SETBOOKMARK ( P_USERID IN VARCHAR2 , P_BOOKMARK IN VARCHAR2 ) AS BEGIN UPDATE T_ER_Bewertung SET LESEZEICHEN = P_BOOKMARK WHERE STAMM_ID in( select regexp_substr(P_USERID,'[^,]+', 1, level) from dual connect by regexp_substr(P_USERID, '[^,]+', 1, level) is not null ) and ER_ID = (select max(ER_ID) from T_ER_Bewertung_Kopie); commit; END UDP_SETBOOKMARK;
然后尝试一下
Begin UDP_SETBOOKMARK ('1,2,3,4,5', 'Test'); End;
你也可以在其他情况下用regexp_substr来使用这个IN-Clauses,试试吧。
我发现最简单的方法是使用FIND_IN_SET
FIND_IN_SET(column_name,values)
值=(1,2,3)
SELECT name WHERE FIND_IN_SET(id,values)
Error 493: The column 'i' that was returned from the nodes() method cannot be used directly. It can only be used with one of the four XML data type methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT NULL checks.
上面的错误是通过使用下面的代码片段在SQL Server 2014中修复的
Declare @Ids varchar(50) Set @Ids = '1,2,3,5,4,6,7,98,234' DECLARE @XML XML SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML) SELECT SomeTable.* FROM SomeTable cross apply @XML.nodes('i') x(i) where SomeTable .Id = xivalue('.', 'VARCHAR(MAX)')
尝试这个:
SELECT ProductId, Name, Tags FROM Product WHERE '1,2,3,' LIKE '%' + CAST(ProductId AS VARCHAR(20)) + ',%';
就像这个链接的最后一个例子所说的那样
最好和简单的方法。
DECLARE @AccumulateKeywordCopy NVARCHAR(2000),@IDDupCopy NVARCHAR(50); SET @AccumulateKeywordCopy =''; SET @IDDupCopy =''; SET @IDDup = (SELECT CONVERT(VARCHAR(MAX), <columnName>) FROM <tableName> WHERE <clause>) SET @AccumulateKeywordCopy = ','+@AccumulateKeyword+','; SET @IDDupCopy = ','+@IDDup +','; SET @IDDupCheck = CHARINDEX(@IDDupCopy,@AccumulateKeywordCopy)