Passando um varchar cheio de valores delimitados por vírgulas para uma function SQL Server IN

Duplicado de
Consulta de valor delimitado por vírgula SQL dynamic
Consultas parametrizadas com como e em

Eu tenho um SQL Server Stored Procedure onde gostaria de passar um varchar cheio de valores delimitados por vírgula para uma function IN . Por exemplo:

 DECLARE @Ids varchar(50); SET @Ids = '1,2,3,5,4,6,7,98,234'; SELECT * FROM sometable WHERE tableid IN (@Ids); 

Isso não funciona, é claro. Eu recebo o erro:

A conversão falhou ao converter o valor varchar ‘1,2,3,5,4,6,7,98,234’ para o tipo de dados int.

Como posso realizar isso (ou algo relativamente similar) sem recorrer à criação de SQL dynamic?

Não use uma function que faça um loop para dividir uma string! , minha function abaixo irá dividir uma string muito rápido, sem loop!

Antes de usar minha function, você precisa configurar uma tabela “auxiliar”, você só precisa fazer isso uma vez por database:

 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 

use esta function para dividir sua string, que não faz loop e é muito rápida:

 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

você pode usar essa function como uma tabela em uma junit:

 SELECT Col1, COl2, Col3... FROM YourTable INNER JOIN FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue 

Aqui está o seu exemplo:

 Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable(',',@Ids) s) 

Claro, se você é preguiçoso como eu, você poderia fazer isso:

 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 

Sem Tabela Sem Função Sem Loop

Com base na idéia de analisar sua lista em uma tabela, nosso DBA sugeriu o uso de XML.

 Declare @Ids varchar(50) Set @Ids = '1,2,3,5,4,6,7,98,234' DECLARE @XML XML SET @XML = CAST('' + REPLACE(@Ids, ',', '') + '' AS XML) SELECT * FROM SomeTable INNER JOIN @XML.nodes('i') x(i) ON SomeTable .Id = xivalue('.', 'VARCHAR(MAX)') 

Estes parecem ter o mesmo desempenho que a resposta do @ KM, mas, penso eu, muito mais simples.

Você pode criar uma function que retorna uma tabela.

então sua declaração seria algo como

 select * from someable join Splitfunction(@ids) as splits on sometable.id = splits.id 

Aqui está uma function simular.

 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 

É uma pergunta muito comum. Resposta enlatada, várias técnicas legais:

http://www.sommarskog.se/arrays-in-sql-2005.html

Isso funciona perfeitamente! As respostas abaixo são muito complicadas. Não olhe para isso como dynamic. Configure o procedimento de armazenamento da seguinte maneira:

 (@id as varchar(50)) as Declare @query as nvarchar(max) set @query =' select * from table where id in('+@id+')' EXECUTE sp_executesql @query 

Sem usar o SQL dynamic, você precisa pegar a variável de input e usar uma function de divisão para colocar os dados em uma tabela temporária e, em seguida, ingressar nela.

Eu posso sugerir usar WITH assim:

 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; 

Obrigado, por sua function eu usei …………………… Este é o meu EXEMPLO

 **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 

Se você usa o SQL Server 2008 ou superior, use parâmetros com valor de tabela; por exemplo:

 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 

Crie uma function de tabela, como abaixo, que analise por vírgula separada varchar e retorne uma tabela que pode ser interna associada a outras tabelas.

 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(('' + REPLACE(s0.prsString, s0.prsSplitDelimit, '') + '') 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 

================================================= Agora consumir acima da function de tabela criada em seu código, criação de function é uma atividade de tempo em seu database que pode ser usada em bancos de dados e no mesmo servidor.

 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 

Eu acho que uma solução muito simples poderia estar seguindo:

 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)+',%'; 

Eu escrevi um procedimento armazenado para mostrar como fazer isso antes. Você basicamente tem que processar a string. Eu tentei postar o código aqui, mas a formatação ficou toda maluca.

 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 

Tem sido um tempo, mas eu fiz isso no passado usando XML como provisório.

Eu não posso levar nenhum crédito por isso, mas eu não tenho mais ideia de onde eu tirei essa ideia:

 -- 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((''+replace(@str,@delimiter ,'')+'') as xml) -- Select back from the XML SELECT N.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as T(N) 

Eu tenho a mesma ideia com o usuário KM. mas não precisa de uma tabela extra. Apenas esta function apenas.

 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 

E aqui está o teste:

 SELECT ListValue FROM dbo.FN_ListToTable('/','a/////bb/c') 

Resultado:

  ListValue ______________________ a bb c 

Tente isto:

 SELECT ProductId, Name, Tags FROM Product WHERE '1,2,3,' LIKE '%' + CAST(ProductId AS VARCHAR(20)) + ',%'; 

Como disse no último exemplo deste link

 -- 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 

Você poderia fazer assim:

 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; 

Então tente com

 Begin UDP_SETBOOKMARK ('1,2,3,4,5', 'Test'); End; 

Você também pode usar essa IN-Clause com regexp_substr em outras situações, apenas tente.

A maneira mais simples que encontrei foi usar FIND_IN_SET

FIND_IN_SET (column_name, valores)

valores = (1,2,3)

SELECT nome WHERE FIND_IN_SET (id, valores)

 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. 

O erro acima foi corrigido no SQL Server 2014 usando o seguinte snippet

 Declare @Ids varchar(50) Set @Ids = '1,2,3,5,4,6,7,98,234' DECLARE @XML XML SET @XML = CAST('' + REPLACE(@Ids, ',', '') + '' AS XML) SELECT SomeTable.* FROM SomeTable cross apply @XML.nodes('i') x(i) where SomeTable .Id = xivalue('.', 'VARCHAR(MAX)') 
 CREATE TABLE t ( id INT, col1 VARCHAR(50) ) INSERT INTO t VALUES (1, 'param1') INSERT INTO t VALUES (2, 'param2') INSERT INTO t VALUES (3, 'param3') INSERT INTO t VALUES (4, 'param4') INSERT INTO t VALUES (5, 'param5') DECLARE @params VARCHAR(100) SET @params = ',param1,param2,param3,' SELECT * FROM t WHERE Charindex(',' + Cast(col1 AS VARCHAR(8000)) + ',', @params) > 0 

trabalho violino encontrar aqui Fiddle

Melhor e simples abordagem.

 DECLARE @AccumulateKeywordCopy NVARCHAR(2000),@IDDupCopy NVARCHAR(50); SET @AccumulateKeywordCopy =''; SET @IDDupCopy =''; SET @IDDup = (SELECT CONVERT(VARCHAR(MAX), ) FROM  WHERE ) SET @AccumulateKeywordCopy = ','+@AccumulateKeyword+','; SET @IDDupCopy = ','+@IDDup +','; SET @IDDupCheck = CHARINDEX(@IDDupCopy,@AccumulateKeywordCopy)