separar - Pasar un varchar lleno de valores delimitados por comas a una función SQL Server IN
split sql (22)
Cree una función de tabla como la de abajo, que descompone en coma y separa varchar y devuelve una tabla que puede combinarse internamente con otras tablas.
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
============================================== Ahora consuma la función de tabla creada anteriormente en su código, la creación de funciones es una actividad única en su base de datos que se puede usar en todas las bases de datos y en el mismo 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
Duplicado de
Consulta de valor delimitado por comas SQL dinámicas
Consultas parametrizadas con Like y In
Tengo un SQL Server Stored Procedure donde me gustaría pasar un varchar
lleno de valores delimitados por comas a una función IN
. Por ejemplo:
DECLARE @Ids varchar(50);
SET @Ids = ''1,2,3,5,4,6,7,98,234'';
SELECT *
FROM sometable
WHERE tableid IN (@Ids);
Esto no funciona, por supuesto. Me sale el error:
La conversión falló al convertir el valor varchar ''1,2,3,5,4,6,7,98,234'' al tipo de datos int.
¿Cómo puedo lograr esto (o algo relativamente similar) sin recurrir a la creación de SQL dinámico?
Creo que una solución muy simple podría ser siguiente:
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)+'',%'';
Es una pregunta muy común. Respuesta enlatada, varias buenas técnicas:
Esto funciona perfectamente! Las respuestas a continuación son muy complicadas. No mires esto como dinámico. Configure su procedimiento de tienda de la siguiente manera:
(@id as varchar(50))
as
Declare @query as nvarchar(max)
set @query =''
select * from table
where id in(''+@id+'')''
EXECUTE sp_executesql @query
Gracias, por su función, lo utilicé ........................ Este es mi EJEMPLO
**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
Ha pasado un tiempo, pero he hecho esto en el pasado usando XML como medida provisional.
No puedo atribuir ningún crédito por esto, pero me temo que ya no sé de dónde saqué esta idea:
-- 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)
He escrito un procedimiento almacenado para mostrar cómo hacerlo antes. Básicamente, debes procesar la cadena. Traté de publicar el código aquí, pero el formato se volvió complicado.
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
La forma más sencilla que encontré fue usar FIND_IN_SET
FIND_IN_SET (column_name, values)
valores = (1,2,3)
SELECCIONE el nombre WHERE FIND_IN_SET (id, valores)
Mejor y simple enfoque.
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)
Podrías hacerlo así:
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;
Entonces pruébalo con
Begin
UDP_SETBOOKMARK (''1,2,3,4,5'', ''Test'');
End;
Puedes usar esta cláusula IN con regexp_substr en otras situaciones también, solo inténtalo.
Por supuesto, si eres flojo como yo, podrías hacer esto:
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
Prueba esto:
SELECT ProductId, Name, Tags
FROM Product
WHERE ''1,2,3,'' LIKE ''%'' + CAST(ProductId AS VARCHAR(20)) + '',%'';
Como se dijo en el último ejemplo de este enlace
Puede crear una función que devuelva una tabla.
por lo que su declaración sería algo así como
select * from someable
join Splitfunction(@ids) as splits on sometable.id = splits.id
Aquí hay una función 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
Puedo sugerir usar WITH
así:
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;
Si usa SQL Server 2008 o una versión superior, use los parámetros de la tabla valorada; por ejemplo:
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
Sin usar SQL dinámico, debe tomar la variable de entrada y usar una función dividida para poner los datos en una tabla temporal y luego unirse a eso.
Tengo la misma idea con el usuario KM. pero no necesita Número de tabla adicional. Solo esta función.
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
Y aquí está la prueba:
SELECT ListValue FROM dbo.FN_ListToTable(''/'',''a/////bb/c'')
Resultado:
ListValue
______________________
a
bb
c
¡No use una función que haga bucles para dividir una cadena! , mi función a continuación dividirá una cadena muy rápido, ¡sin bucle!
Antes de usar mi función, necesita configurar una tabla de "ayuda", solo necesita hacer esto una vez por base de datos:
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
usa esta función para dividir tu cadena, que no se repite y es muy 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
puede usar esta función como una tabla en una unión:
SELECT
Col1, COl2, Col3...
FROM YourTable
INNER JOIN FN_ListToTable('','',@YourString) s ON YourTable.ID = s.ListValue
Aquí está su ejemplo:
Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable('','',@Ids) s)
Sin tabla Sin función Sin lazo
Partiendo de la idea de analizar su lista en una tabla, nuestro DBA sugirió usar 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 = x.i.value(''.'', ''VARCHAR(MAX)'')
Estos parecen tener el mismo rendimiento que la respuesta de @ KM pero, creo, mucho más simple.
-- 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 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
violín que funciona aquí Fiddle
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.
El error anterior se corrigió en SQL Server 2014 utilizando el siguiente fragmento
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 = x.i.value(''.'', ''VARCHAR(MAX)'')