Con SQL Server, ¿cómo divido una cadena para poder acceder al elemento x?
Tome una cadena "Hola John Smith". ¿Cómo puedo dividir la cadena por espacio y acceder al elemento en el índice 1 que debería devolver "John"?
Con SQL Server, ¿cómo divido una cadena para poder acceder al elemento x?
Tome una cadena "Hola John Smith". ¿Cómo puedo dividir la cadena por espacio y acceder al elemento en el índice 1 que debería devolver "John"?
Respuestas:
Puede encontrar útil la solución en la función definida por el usuario de SQL para analizar una cadena delimitada (desde The Code Project ).
Puedes usar esta lógica simple:
Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null
WHILE LEN(@products) > 0
BEGIN
IF PATINDEX('%|%', @products) > 0
BEGIN
SET @individual = SUBSTRING(@products,
0,
PATINDEX('%|%', @products))
SELECT @individual
SET @products = SUBSTRING(@products,
LEN(@individual + '|') + 1,
LEN(@products))
END
ELSE
BEGIN
SET @individual = @products
SET @products = NULL
SELECT @individual
END
END
SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))
no SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
que dividirá una cadena y devolverá un resultado de tabla de una columna que puede usar en una SELECT
declaración o en otro lugar.
No creo que SQL Server tenga una función de división integrada, por lo que, aparte de un UDF, la única otra respuesta que conozco es secuestrar la función PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME toma una cadena y la divide en el carácter de punto. Toma un número como su segundo argumento, y ese número especifica qué segmento de la cadena devolver (trabajando de atrás hacia adelante).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
El problema obvio es cuando la cadena ya contiene un punto. Sigo pensando que usar un UDF es la mejor manera ... ¿alguna otra sugerencia?
SPLIT()
No se proporciona una función porque fomenta el diseño deficiente de la base de datos, y la base de datos nunca se optimizará para usar datos almacenados en este formato. El RDBMS no está obligado a ayudar a los desarrolladores a hacer cosas estúpidas para las que ha sido diseñado para no manejar. La respuesta correcta siempre será "Normalice su base de datos como le dijimos hace 40 años". Ni SQL ni RDBMS tienen la culpa de un diseño deficiente.
Primero, cree una función (usando CTE, la expresión de tabla común elimina la necesidad de una tabla temporal)
create function dbo.SplitString
(
@str nvarchar(4000),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
1,
1,
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 zeroBasedOccurance,
substring(
@str,
a,
case when b > 0 then b-a ELSE 4000 end)
AS s
from tokens
)
GO
Luego, úselo como cualquier tabla (o modifíquelo para que se ajuste a su proceso almacenado existente) como este.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Actualizar
La versión anterior fallaría para una cadena de entrada de más de 4000 caracteres. Esta versión se encarga de la limitación:
create function dbo.SplitString
(
@str nvarchar(max),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
cast(1 as bigint),
cast(1 as bigint),
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 ItemIndex,
substring(
@str,
a,
case when b > 0 then b-a ELSE LEN(@str) end)
AS s
from tokens
);
GO
El uso sigue siendo el mismo.
100
(para evitar el bucle infinito). Use la sugerencia MAXRECURSION para definir el número de niveles de recursión ( 0
hasta 32767
, 0
es "sin límite", puede aplastar el servidor). Por cierto, mucho mejor respuesta que PARSENAME
, porque es universal :-). +1
maxrecursion
de esta solución, tenga en cuenta esta pregunta y sus respuestas. Cómo configurar la maxrecursion
opción para un CTE dentro de una función con valores de tabla .
s
lo que ya no está definido
La mayoría de las soluciones aquí usan bucles while o CTE recursivos. Un enfoque basado en conjuntos será superior, lo prometo, si puede usar un delimitador que no sea un espacio:
CREATE FUNCTION [dbo].[SplitString]
(
@List NVARCHAR(MAX),
@Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM
(
SELECT n = Number,
[Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(@List)
AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
) AS y
);
Uso de la muestra:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Resultados:
----
blat
También puedes agregar el idx
que desee como argumento a la función, pero lo dejaré como ejercicio para el lector.
No puede hacer esto solo con la función nativaSTRING_SPLIT
agregada en SQL Server 2016, porque no hay garantía de que la salida se procesará en el orden de la lista original. En otras palabras, si pasa 3,6,1
el resultado, es probable que esté en ese orden, pero podría serlo 1,3,6
. He pedido la ayuda de la comunidad para mejorar la función incorporada aquí:
Con suficientes comentarios cualitativos , en realidad pueden considerar hacer algunas de estas mejoras:
Más información sobre las funciones divididas, por qué (y prueba de ello) mientras que los bucles y los CTE recursivos no se escalan, y mejores alternativas, si las cadenas de división provienen de la capa de aplicación:
Sin embargo, en SQL Server 2016 o superior, debe mirar STRING_SPLIT()
y STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
y la salida producida fue: Valor Hola llo llo o John ohn hn n smith mith ith th h
Puede aprovechar una tabla de números para analizar las cadenas.
Crea una tabla de números físicos:
create table dbo.Numbers (N int primary key);
insert into dbo.Numbers
select top 1000 row_number() over(order by number) from master..spt_values
go
Crear tabla de prueba con 1000000 filas
create table #yak (i int identity(1,1) primary key, array varchar(50))
insert into #yak(array)
select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
go
Crea la función
create function [dbo].[ufn_ParseArray]
( @Input nvarchar(4000),
@Delimiter char(1) = ',',
@BaseIdent int
)
returns table as
return
( select row_number() over (order by n asc) + (@BaseIdent - 1) [i],
substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
from dbo.Numbers
where n <= convert(int, len(@Input)) and
substring(@Delimiter + @Input, n, 1) = @Delimiter
)
go
Uso (produce 3mil filas en 40s en mi computadora portátil)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
limpiar
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
El rendimiento aquí no es sorprendente, pero llamar a una función en una tabla de un millón de filas no es la mejor idea. Si realiza una cadena dividida en muchas filas, evitaría la función.
desc
se eliminaran?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
desde @NothingsImpossible se completó en 1,5 minutos. @hello_earth ¿Cómo se compararía su solución en cadenas más largas con más de 4 campos?
Esta pregunta es no se trata de un enfoque de división de cadenas , sino de cómo obtener el enésimo elemento .
Todas las respuestas aquí están haciendo algún tipo de división de cadenas usando recursividad, CTE
s, múltipleCHARINDEX
, REVERSE
y PATINDEX
, inventando funciones, llamada a los métodos de CLR, tablas de números, CROSS APPLY
es ... La mayoría de las respuestas cubre muchas líneas de código.
Pero, si realmente no quieres nada más que un enfoque para obtener el enésimo elemento , esto se puede hacer como una sola línea real , sin UDF, ni siquiera una sub-selección ... Y como un beneficio adicional: escriba seguro
Obtenga la parte 2 delimitada por un espacio:
DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')
Por supuesto , puede usar variables para delimitador y posición (usesql:column
para recuperar la posición directamente del valor de una consulta):
DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')
Si su cadena puede incluir caracteres prohibidos (especialmente uno entre &><
), aún puede hacerlo de esta manera. Solo use FOR XML PATH
en su cadena primero para reemplazar todos los caracteres prohibidos con la secuencia de escape adecuada implícitamente.
Es un caso muy especial si, además, su delimitador es el punto y coma . En este caso, reemplazo el delimitador primero en '# DLMT #' y finalmente lo reemplazo por las etiquetas XML:
SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');
Lamentablemente, los desarrolladores olvidaron devolver el índice de la pieza STRING_SPLIT
. Pero, usando SQL-Server 2016+, hayJSON_VALUE
y OPENJSON
.
Con JSON_VALUE
podemos pasar en la posición como el índice 'matriz.
Para OPENJSON
la documentación indica claramente:
Cuando OPENJSON analiza una matriz JSON, la función devuelve los índices de los elementos en el texto JSON como claves.
Una cadena como 1,2,3
necesita nada más que entre paréntesis: [1,2,3]
.
Una cadena de palabras como this is an example
debe ser ["this","is","an","example"]
.
Estas son operaciones de cadena muy fáciles. Solo pruébalo:
DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;
--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));
- Vea esto para un separador de cadenas seguro para la posición ( basado en cero ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
En esta publicación probé varios enfoques y descubrí que OPENJSON
es realmente rápido. Incluso mucho más rápido que el famoso método "delimitedSplit8k ()" ...
Podemos usar una matriz dentro de una matriz simplemente usando doubled [[]]
. Esto permite una WITH
cláusula escrita :
DECLARE @SomeDelimitedString VARCHAR(100)='part1|1|20190920';
DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');
SELECT @SomeDelimitedString AS TheOriginal
,@JsonArray AS TransformedToJSON
,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment VARCHAR(100) '$[0]'
,TheSecondFragment INT '$[1]'
,TheThirdFragment DATE '$[2]') ValuesFromTheArray
<x><![CDATA[x<&>x]]></x>
.
CDATA
secciones también pueden ocuparse de esto ... Pero después del elenco se han ido (cambiado para escapar text()
implícitamente). No me gusta la magia debajo del capó , así que preferiría el (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
enfoque. Esto me parece más limpio y sucede de todos modos ... (Un poco más sobre CDATA y XML ).
Aquí hay un UDF que lo hará. Devolverá una tabla de los valores delimitados, no he probado todos los escenarios, pero su ejemplo funciona bien.
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
@myString varchar(500),
@deliminator varchar(10)
)
RETURNS
@ReturnTable TABLE
(
-- Add the column definitions for the TABLE variable here
[id] [int] IDENTITY(1,1) NOT NULL,
[part] [varchar](50) NULL
)
AS
BEGIN
Declare @iSpaces int
Declare @part varchar(50)
--initialize spaces
Select @iSpaces = charindex(@deliminator,@myString,0)
While @iSpaces > 0
Begin
Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))
Insert Into @ReturnTable(part)
Select @part
Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))
Select @iSpaces = charindex(@deliminator,@myString,0)
end
If len(@myString) > 0
Insert Into @ReturnTable
Select @myString
RETURN
END
GO
Lo llamarías así:
Select * From SplitString('Hello John Smith',' ')
Editar: Solución actualizada para manejar delimitadores con un len> 1 como en:
select * From SplitString('Hello**John**Smith','**')
Aquí publico una forma simple de solución
CREATE FUNCTION [dbo].[split](
@delimited NVARCHAR(MAX),
@delimiter NVARCHAR(100)
) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE @xml XML
SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'
INSERT INTO @t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM @xml.nodes('/t') as records(r)
RETURN
END
Ejecute la función así
select * from dbo.split('Hello John Smith',' ')
En mi opinión, ustedes lo están haciendo demasiado complicado. Simplemente cree un CLR UDF y termine con él.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;
public partial class UserDefinedFunctions {
[SqlFunction]
public static SqlString SearchString(string Search) {
List<string> SearchWords = new List<string>();
foreach (string s in Search.Split(new char[] { ' ' })) {
if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
SearchWords.Add(s);
}
}
return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
}
};
¿Qué pasa con el uso string
y la values()
declaración?
DECLARE @str varchar(max)
SET @str = 'Hello John Smith'
DECLARE @separator varchar(max)
SET @separator = ' '
DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))
SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)'
INSERT INTO @Splited
EXEC(@str)
SELECT * FROM @Splited
Conjunto de resultados alcanzado.
id item
1 Hello
2 John
3 Smith
Uso la respuesta de frederic pero esto no funcionó en SQL Server 2005
Lo modifiqué y estoy usando select
con union all
y funciona
DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'
DECLARE @separator varchar(max)
SET @separator = ' '
DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))
SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT ''' + @str + ''' '
INSERT INTO @Splited
EXEC(@str)
SELECT * FROM @Splited
Y el conjunto de resultados es:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
llama implícitamente a un procedimiento almacenado, y no puede usar procedimientos almacenados en UDF.
Este patrón funciona bien y puedes generalizar
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
nota CAMPO , ÍNDICE y TIPO .
Deje alguna tabla con identificadores como
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Entonces, puedes escribir
SELECT Source = q.value('(/n[1])', 'varchar(10)'),
RecordType = q.value('(/n[2])', 'varchar(20)'),
RecordNumber = q.value('(/n[3])', 'int'),
Status = q.value('(/n[4])', 'varchar(5)')
FROM (
SELECT q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
FROM some_TABLE
) Q
División y fundición de todas las partes.
Si su base de datos tiene un nivel de compatibilidad de 130 o superior, puede usar la función STRING_SPLIT junto con OFFSET FETCH cláusulas para obtener el elemento específico por índice.
Para obtener el elemento en el índice N (basado en cero), puede usar el siguiente código
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Para verificar el nivel de compatibilidad de su base de datos , ejecute este código:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
usé esto antes ... Es bueno saberlo ... Todavía preferiría el enfoque basado en -split, ya que permite obtener el valor de tipo seguro y no necesita una subconsulta, pero esta es una bueno +1 de mi lado
STRING_SPLIT
demandas para v2016 +. En este caso, es mucho mejor usar OPENJSON
o JSON_VALUE
. Es posible que desee comprobar mi respuesta
Estaba buscando la solución en la red y lo siguiente funciona para mí. Ref .
Y llamas a la función así:
SELECT * FROM dbo.split('ram shyam hari gopal',' ')
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))
RETURNS @temptable TABLE (items VARCHAR(8000))
AS
BEGIN
DECLARE @idx INT
DECLARE @slice VARCHAR(8000)
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(Items) VALUES(@slice)
SET @String = RIGHT(@String,len(@String) - @idx)
IF len(@String) = 0 break
END
RETURN
END
Sin embargo, otro obtiene la enésima parte de la cadena por función delimitador:
create function GetStringPartByDelimeter (
@value as nvarchar(max),
@delimeter as nvarchar(max),
@position as int
) returns NVARCHAR(MAX)
AS BEGIN
declare @startPos as int
declare @endPos as int
set @endPos = -1
while (@position > 0 and @endPos != 0) begin
set @startPos = @endPos + 1
set @endPos = charindex(@delimeter, @value, @startPos)
if(@position = 1) begin
if(@endPos = 0)
set @endPos = len(@value) + 1
return substring(@value, @startPos, @endPos - @startPos)
end
set @position = @position - 1
end
return null
end
y el uso:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
que devuelve:
c
Prueba esto:
CREATE function [SplitWordList]
(
@list varchar(8000)
)
returns @t table
(
Word varchar(50) not null,
Position int identity(1,1) not null
)
as begin
declare
@pos int,
@lpos int,
@item varchar(100),
@ignore varchar(100),
@dl int,
@a1 int,
@a2 int,
@z1 int,
@z2 int,
@n1 int,
@n2 int,
@c varchar(1),
@a smallint
select
@a1 = ascii('a'),
@a2 = ascii('A'),
@z1 = ascii('z'),
@z2 = ascii('Z'),
@n1 = ascii('0'),
@n2 = ascii('9')
set @ignore = '''"'
set @pos = 1
set @dl = datalength(@list)
set @lpos = 1
set @item = ''
while (@pos <= @dl) begin
set @c = substring(@list, @pos, 1)
if (@ignore not like '%' + @c + '%') begin
set @a = ascii(@c)
if ((@a >= @a1) and (@a <= @z1))
or ((@a >= @a2) and (@a <= @z2))
or ((@a >= @n1) and (@a <= @n2))
begin
set @item = @item + @c
end else if (@item > '') begin
insert into @t values (@item)
set @item = ''
end
end
set @pos = @pos + 1
end
if (@item > '') begin
insert into @t values (@item)
end
return
end
Pruébalo así:
select * from SplitWordList('Hello John Smith')
El siguiente ejemplo usa un CTE recursivo
Actualización 18.09.2013
CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
(
SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter, @List + @Delimiter)) AS val,
CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval,
1 AS [level]
UNION ALL
SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
[level] + 1
FROM cte
WHERE stval != ''
)
INSERT @returns
SELECT REPLACE(val, ' ','' ) AS val, [level]
FROM cte
WHERE val > ''
RETURN
END
Demostración en SQLFiddle
Alter Function dbo.fn_Split
(
@Expression nvarchar(max),
@Delimiter nvarchar(20) = ',',
@Qualifier char(1) = Null
)
RETURNS @Results TABLE (id int IDENTITY(1,1), value nvarchar(max))
AS
BEGIN
/* USAGE
Select * From dbo.fn_Split('apple pear grape banana orange honeydew cantalope 3 2 1 4', ' ', Null)
Select * From dbo.fn_Split('1,abc,"Doe, John",4', ',', '"')
Select * From dbo.fn_Split('Hello 0,"&""&&&&', ',', '"')
*/
-- Declare Variables
DECLARE
@X xml,
@Temp nvarchar(max),
@Temp2 nvarchar(max),
@Start int,
@End int
-- HTML Encode @Expression
Select @Expression = (Select @Expression For XML Path(''))
-- Find all occurences of @Delimiter within @Qualifier and replace with |||***|||
While PATINDEX('%' + @Qualifier + '%', @Expression) > 0 AND Len(IsNull(@Qualifier, '')) > 0
BEGIN
Select
-- Starting character position of @Qualifier
@Start = PATINDEX('%' + @Qualifier + '%', @Expression),
-- @Expression starting at the @Start position
@Temp = SubString(@Expression, @Start + 1, LEN(@Expression)-@Start+1),
-- Next position of @Qualifier within @Expression
@End = PATINDEX('%' + @Qualifier + '%', @Temp) - 1,
-- The part of Expression found between the @Qualifiers
@Temp2 = Case When @End < 0 Then @Temp Else Left(@Temp, @End) End,
-- New @Expression
@Expression = REPLACE(@Expression,
@Qualifier + @Temp2 + Case When @End < 0 Then '' Else @Qualifier End,
Replace(@Temp2, @Delimiter, '|||***|||')
)
END
-- Replace all occurences of @Delimiter within @Expression with '</fn_Split><fn_Split>'
-- And convert it to XML so we can select from it
SET
@X = Cast('<fn_Split>' +
Replace(@Expression, @Delimiter, '</fn_Split><fn_Split>') +
'</fn_Split>' as xml)
-- Insert into our returnable table replacing '|||***|||' back to @Delimiter
INSERT @Results
SELECT
"Value" = LTRIM(RTrim(Replace(C.value('.', 'nvarchar(max)'), '|||***|||', @Delimiter)))
FROM
@X.nodes('fn_Split') as X(C)
-- Return our temp table
RETURN
END
Puede dividir una cadena en SQL sin necesidad de una función:
DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'
-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT
x.XmlCol.value('.', 'varchar(36)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);
Si necesita admitir cadenas arbitrarias (con caracteres especiales xml)
DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "Conex"<html>,Barnes & Noble,abc,def,ghi'
-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT
x.XmlCol.value('.', 'nvarchar(MAX)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);
Sé que es una vieja pregunta, pero creo que alguien puede beneficiarse de mi solución.
select
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
,1
,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
,LEN(column_name))
from table_name
Ventajas:
Limitaciones:
Nota : la solución puede dar subcadenas hasta N.
Para superar la limitación, podemos usar la siguiente referencia .
Pero, de nuevo, la solución anterior no se puede usar en una tabla (Actaully no pude usarla).
De nuevo, espero que esta solución pueda ayudar a alguien.
Actualización: en el caso de registros> 50000, no es aconsejable usarlo, LOOPS
ya que degradará el rendimiento
Solución pura basada en conjuntos usando TVF
con recursivo CTE
. Puede JOIN
y APPLY
esta función a cualquier conjunto de datos.
create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
union all
select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
, left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
, [no] + 1 [no]
from r where value > '')
select ltrim(x) [value], [no] [index] from r where x is not null;
go
Uso:
select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;
Resultado:
value index
-------------
John 1
Casi todas las otras respuestas están reemplazando la cadena que se divide, lo que desperdicia los ciclos de la CPU y realiza asignaciones de memoria innecesarias.
Cubro una forma mucho mejor de hacer una división de cadenas aquí: http://www.digitalruby.com/split-string-sql-server/
Aquí está el código:
SET NOCOUNT ON
-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1
SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
WHILE @SplitEndPos > 0
BEGIN
SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
INSERT @SplitStringTable (Value) VALUES (@SplitValue)
SET @SplitStartPos = @SplitEndPos + 1
SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END
SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)
SET NOCOUNT OFF
-- You can select or join with the values in @SplitStringTable at this point.
Solución CTE recursiva con dolor de servidor, pruébela
Configuración del esquema de MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Consulta 1 :
with cte as
( select
left( Courses, charindex( ' ' , Courses) ) as a_l,
cast( substring( Courses,
charindex( ' ' , Courses) + 1 ,
len(Courses ) ) + ' '
as varchar(100) ) as a_r,
Courses as a,
0 as n
from Course t
union all
select
left(a_r, charindex( ' ' , a_r) ) as a_l,
substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
cte.a,
cte.n + 1 as n
from Course t inner join cte
on t.Courses = cte.a and len( a_r ) > 0
)
select a_l, n from cte
--where N = 1
| A_L | N |
|--------|---|
| Hello | 0 |
| John | 1 |
| Smith | 2 |
Si bien es similar a la respuesta basada en xml de josejuan, descubrí que procesar la ruta xml solo una vez, luego pivotar fue moderadamente más eficiente:
select ID,
[3] as PathProvidingID,
[4] as PathProvider,
[5] as ComponentProvidingID,
[6] as ComponentProviding,
[7] as InputRecievingID,
[8] as InputRecieving,
[9] as RowsPassed,
[10] as InputRecieving2
from
(
select id,message,d.* from sysssislog cross apply (
SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
row_number() over(order by y.i) as rn
FROM
(
SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
) d
WHERE event
=
'OnPipelineRowsSent'
) as tokens
pivot
( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10])
) as data
corrió a las 8:30
select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
from
(
select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
from sysssislog
WHERE event
=
'OnPipelineRowsSent'
) as data
corrió en 9:20
CREATE FUNCTION [dbo].[fnSplitString]
(
@string NVARCHAR(MAX),
@delimiter CHAR(1)
)
RETURNS @output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE @start INT, @end INT
SELECT @start = 1, @end = CHARINDEX(@delimiter, @string)
WHILE @start < LEN(@string) + 1 BEGIN
IF @end = 0
SET @end = LEN(@string) + 1
INSERT INTO @output (splitdata)
VALUES(SUBSTRING(@string, @start, @end - @start))
SET @start = @end + 1
SET @end = CHARINDEX(@delimiter, @string, @start)
END
RETURN
END
Y USARLO
select *from dbo.fnSplitString('Querying SQL Server','')
si alguien quiere obtener solo una parte del texto separado puede usar esto
seleccione * de fromSplitStringSep ('Word1 wordr2 word3', '')
CREATE function [dbo].[SplitStringSep]
(
@str nvarchar(4000),
@separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
1,
1,
charindex(@separator, @str)
union all
select
p + 1,
b + 1,
charindex(@separator, @str, b + 1)
from tokens
where b > 0
)
select
p-1 zeroBasedOccurance,
substring(
@str,
a,
case when b > 0 then b-a ELSE 4000 end)
AS s
from tokens
)
Desarrollé esto,
declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';
while CHARINDEX(@splitter,@x) != 0
begin
set @item = LEFT(@x,CHARINDEX(@splitter,@x))
set @x = RIGHT(@x,len(@x)-len(@item) )
select @item as item, @x as x;
end
la única atención que debes hacer es dot '.' ese final de la @x siempre debe estar allí.
basándose en la solución @NothingsImpossible, o, más bien, comentando la respuesta más votada (justo debajo de la respuesta aceptada), encontré la siguiente pregunta rápida y sucia solución satisface mis propias necesidades: tiene el beneficio de estar únicamente dentro del dominio SQL.
dado una cadena "primero; segundo; tercero; cuarto; quinto", digamos, quiero obtener el tercer token esto funciona solo si sabemos cuántos tokens tendrá la cadena, en este caso son 5. así que mi forma de acción es cortar los dos últimos tokens (consulta interna) y luego cortar los dos primeros tokens ( consulta externa)
Sé que esto es feo y cubre las condiciones específicas en las que estaba, pero lo estoy publicando en caso de que alguien lo encuentre útil. salud
select
REVERSE(
SUBSTRING(
reverse_substring,
0,
CHARINDEX(';', reverse_substring)
)
)
from
(
select
msg,
SUBSTRING(
REVERSE(msg),
CHARINDEX(
';',
REVERSE(msg),
CHARINDEX(
';',
REVERSE(msg)
)+1
)+1,
1000
) reverse_substring
from
(
select 'first;second;third;fourth;fifth' msg
) a
) b
A partir de SQL Server 2016 , string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
no garantiza devolver el mismo pedido. Pero OPENJSON
sí (ver mi respuesta (sección de actualización) )