¿Cómo divido una cadena para poder acceder al elemento x?
-
08-06-2019 - |
Pregunta
Usando 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"?
Solución
Puedes encontrar la solución en Función definida por el usuario de SQL para analizar una cadena delimitada útil (de El proyecto del código).
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
Otros consejos
No creo que SQL Server tenga una función de división incorporada, por lo que, aparte de una 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 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 una UDF es la mejor manera... ¿alguna otra sugerencia?
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, úsela como cualquier tabla (o modifíquela para que se ajuste a su proceso almacenado existente) como esta.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Actualizar
La versión anterior fallaría si la cadena de entrada tuviera 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.
La mayoría de las soluciones aquí utilizan bucles while o CTE recursivos.Un enfoque basado en conjuntos será superior, lo prometo:
CREATE FUNCTION [dbo].[SplitString]
(
@List NVARCHAR(MAX),
@Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT
[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
);
Más información sobre funciones divididas, por qué (y prueba de ello) los bucles while y los CTE recursivos no se escalan, y mejores alternativas, si se dividen cadenas provenientes de la capa de aplicación:
- Divida las cuerdas de la manera correcta, o de la siguiente mejor manera
- División de cuerdas:Un seguimiento
- División de cuerdas:Ahora con menos T-SQL
- Comparación de métodos de división/concatenación de cadenas
- Procesando una lista de números enteros:mi acercamiento
- Dividiendo una lista de números enteros:otro resumen
- Más información sobre cómo dividir listas:delimitadores personalizados, prevención de duplicados y mantenimiento del orden
- Eliminar duplicados de cadenas en SQL Server
Sin embargo, en SQL Server 2016 o superior, deberías mirar STRING_SPLIT()
y STRING_AGG()
:
Puede aprovechar una tabla numérica para realizar el análisis de 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
Crear 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 (genera 3 mil filas en 40 segundos 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 realizara una cadena dividida en muchas filas, evitaría la función.
Esta pregunta es no se trata de un enfoque de división de cuerdas, pero sobre cómo obtener el enésimo elemento.
Todas las respuestas aquí están haciendo algún tipo de división de cadenas mediante recursividad, CTE
s, múltiples CHARINDEX
, REVERSE
y PATINDEX
, inventar funciones, llamar a métodos CLR, tablas numéricas, CROSS APPLY
s ...La mayoría de las respuestas cubren muchas líneas de código.
Pero - si realmente No quiero nada más que un enfoque para obtener el enésimo elemento. - esto se puede hacer como verdadera frase, sin UDF, ni siquiera una subselección...Y como beneficio extra: tipo 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 puedes usar variables para delimitador y posición (use sql: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 podría incluir caracteres prohibidos (especialmente uno entre &><
), aún puedes hacerlo de esta manera.Solo usa FOR XML PATH
en su cadena primero para reemplazar implícitamente todos los caracteres prohibidos con la secuencia de escape adecuada.
Es un caso muy especial si - además - tu delimitador es el punto y coma.En este caso, reemplazo el delimitador primero por '#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)');
ACTUALIZACIÓN para SQL-Server 2016+
Lamentablemente, los desarrolladores olvidaron devolver el índice de la pieza con STRING_SPLIT
.Pero, al utilizar SQL-Server 2016+, existe OPENJSON
.
El documentación dice claramente:
Cuando OPENJSON analiza una matriz JSON, la función devuelve los índices de los elementos en el texto JSON como claves.
una cuerda como 1,2,3
no necesita nada más que corchetes: [1,2,3]
.
Una serie de palabras como this is an example
necesita ser ["this","is","an"," example"]
.
Estas son operaciones de cuerdas muy sencillas.Pruébalo:
DECLARE @str VARCHAR(100)='Hello John Smith';
SELECT [value]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]')
WHERE [key]=1 --zero-based!
Aquí hay una UDF que lo hará.Devolverá una tabla de 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 len>1 como en:
select * From SplitString('Hello**John**Smith','**')
Aquí publico una forma sencilla 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 una UDF CLR y listo.
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 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
Utilizo 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
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.
Dejemos 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
dividir y fundir todas las piezas.
Si su base de datos tiene un nivel de compatibilidad de 130 o superior, puede utilizar el STRING_SPLIT funcionar junto con BÚSQUEDA DE COMPENSACIÓN cláusulas para obtener el elemento específico por índice.
Para obtener el artículo en índice sustantivo (basado en cero), puede utilizar 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 comprobar el nivel de compatibilidad de su base de datos, ejecuta este código:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
Estaba buscando la solución en la red y lo siguiente me funciona.Árbitro.
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
Otra función más para obtener la enésima parte de la cadena mediante 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ébelo así:
select * from SplitWordList('Hello John Smith')
El siguiente ejemplo utiliza un CTE recursivo
Actualizar 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
Sé que es una pregunta antigua, 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:
- Separa las 3 subcadenas delimitadoras por ' '.
- No se debe utilizar el bucle while, ya que disminuye el rendimiento.
- No hay necesidad de pivotar, ya que toda la sub-string resultante se mostrará en una fila
Limitaciones:
- Hay que saber el número total.de espacios (subcadena).
Nota:la solución puede dar una subcadena de hasta N.
Para superar la limitación podemos utilizar lo siguiente árbitro.
Pero nuevamente lo anterior solución no se puede usar en una mesa (en realidad no pude usarlo).
Nuevamente espero que esta solución pueda ayudar a alguien.
Actualizar: En caso de Registros > 50000 no es aconsejable usar LOOPS
ya que degradará el Actuación
Casi todas las demás respuestas del código dividido reemplazan la cadena que se divide, lo que desperdicia ciclos de CPU y realiza asignaciones de memoria innecesarias.
Cubro una manera mucho mejor de dividir una cadena 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.
Puedes 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);
Solución pura basada en conjuntos que utiliza 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
Empezando con Servidor SQL 2016 nosotros cadena_dividida
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
Un enfoque moderno que utiliza STRING_SPLIT, requiere SQL Server 2016 y superior.
DECLARE @string varchar(100) = 'Hello John Smith'
SELECT
ROW_NUMBER() OVER (ORDER BY value) AS RowNr,
value
FROM string_split(@string, ' ')
Resultado:
RowNr value
1 Hello
2 John
3 Smith
Ahora es posible obtener el enésimo elemento del número de fila.
La respuesta de Aaron Bertrand es excelente, pero defectuosa.No maneja con precisión un espacio como delimitador (como era el ejemplo en la pregunta original) ya que la función de longitud elimina los espacios finales.
El siguiente es su código, con un pequeño ajuste para permitir un delimitador de espacio:
CREATE FUNCTION [dbo].[SplitString]
(
@List NVARCHAR(MAX),
@Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT
[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+'x')-1) = @Delim
) AS y
);
Aquí hay una función que logrará el objetivo de la pregunta de dividir una cadena y acceder al elemento X:
CREATE FUNCTION [dbo].[SplitString]
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255),
@ElementNumber INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE @inp VARCHAR(MAX)
SET @inp = (SELECT REPLACE(@List,@Delimiter,'_DELMTR_') FOR XML PATH(''))
DECLARE @xml XML
SET @xml = '<split><el>' + REPLACE(@inp,'_DELMTR_','</el><el>') + '</el></split>'
DECLARE @ret VARCHAR(MAX)
SET @ret = (SELECT
el = split.el.value('.','varchar(max)')
FROM @xml.nodes('/split/el[string-length(.)>0][position() = sql:variable("@elementnumber")]') split(el))
RETURN @ret
END
Uso:
SELECT dbo.SplitString('Hello John Smith', ' ', 2)
Resultado:
John
SOLUCIÓN SIMPLE PARA ANALIZAR NOMBRE Y APELLIDO
DECLARE @Name varchar(10) = 'John Smith'
-- Get First Name
SELECT SUBSTRING(@Name, 0, (SELECT CHARINDEX(' ', @Name)))
-- Get Last Name
SELECT SUBSTRING(@Name, (SELECT CHARINDEX(' ', @Name)) + 1, LEN(@Name))
En mi caso (y en muchos otros parece...), tengo una lista de nombres y apellidos separados por un solo espacio.Esto se puede usar directamente dentro de una declaración de selección para analizar el nombre y apellido.
-- i.e. Get First and Last Name from a table of Full Names
SELECT SUBSTRING(FullName, 0, (SELECT CHARINDEX(' ', FullName))) as FirstName,
SUBSTRING(FullName, (SELECT CHARINDEX(' ', FullName)) + 1, LEN(FullName)) as LastName,
From FullNameTable
Sé que es tarde, pero recientemente tuve este requisito y se me ocurrió el siguiente código.No tengo la opción de utilizar la función definida por el usuario.Espero que esto ayude.
SELECT
SUBSTRING(
SUBSTRING('Hello John Smith' ,0,CHARINDEX(' ','Hello John Smith',CHARINDEX(' ','Hello John Smith')+1)
),CHARINDEX(' ','Hello John Smith'),LEN('Hello John Smith')
)
Bueno, el mío no es tan simple, pero aquí está el código que uso para dividir una variable de entrada delimitada por comas en valores individuales y ponerla en una variable de tabla.Estoy seguro de que podría modificar esto ligeramente para dividirlo según un espacio y luego realizar una consulta SELECT básica en esa variable de la tabla para obtener los resultados.
-- Create temporary table to parse the list of accounting cycles.
DECLARE @tblAccountingCycles table
(
AccountingCycle varchar(10)
)
DECLARE @vchAccountingCycle varchar(10)
DECLARE @intPosition int
SET @vchAccountingCycleIDs = LTRIM(RTRIM(@vchAccountingCycleIDs)) + ','
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
IF REPLACE(@vchAccountingCycleIDs, ',', '') <> ''
BEGIN
WHILE @intPosition > 0
BEGIN
SET @vchAccountingCycle = LTRIM(RTRIM(LEFT(@vchAccountingCycleIDs, @intPosition - 1)))
IF @vchAccountingCycle <> ''
BEGIN
INSERT INTO @tblAccountingCycles (AccountingCycle) VALUES (@vchAccountingCycle)
END
SET @vchAccountingCycleIDs = RIGHT(@vchAccountingCycleIDs, LEN(@vchAccountingCycleIDs) - @intPosition)
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
END
END
El concepto es prácticamente el mismo.Otra alternativa es aprovechar la compatibilidad .NET dentro del propio SQL Server 2005.Básicamente, puede escribir usted mismo un método simple en .NET que dividiría la cadena y luego la expondría como un procedimiento/función almacenado.
Esto es algo que hice para obtener un token específico en una cadena.(Probado en MSSQL 2008)
Primero, creando las siguientes funciones:(encontrado en: aquí
CREATE FUNCTION dbo.SplitStrings_Moden
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E42(N) AS (SELECT 1 FROM E4 a, E2 b),
cteTally(N) AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1)))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
FROM cteStart s;
y
create FUNCTION dbo.getToken
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255),
@Pos int
)
RETURNS varchar(max)
as
begin
declare @returnValue varchar(max);
select @returnValue = tbl.Item from (
select ROW_NUMBER() over (order by (select null)) as id, * from dbo.SplitStrings_Moden(@List, @Delimiter)
) as tbl
where tbl.id = @Pos
return @returnValue
end
entonces puedes usarlo así:
select dbo.getToken('1111_2222_3333_', '_', 1)
que devuelven 1111