Usando o SQL Server, como faço para dividir uma sequência para acessar o item x?
Pegue uma string "Olá John Smith". Como posso dividir a string por espaço e acessar o item no índice 1, que deve retornar "John"?
Usando o SQL Server, como faço para dividir uma sequência para acessar o item x?
Pegue uma string "Olá John Smith". Como posso dividir a string por espaço e acessar o item no índice 1, que deve retornar "John"?
Respostas:
Você pode encontrar a solução na Função Definida pelo Usuário do SQL para Analisar uma Cadeia de caracteres Delimitada útil (no The Code Project ).
Você pode usar esta lógica simples:
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)))
e não SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
que dividirá uma sequência e retornará um resultado da tabela de uma coluna que você pode usar em uma SELECT
instrução ou em outro local.
Não acredito que o SQL Server tenha uma função de divisão interna; portanto, além de uma UDF, a única outra resposta que eu sei é seqüestrar a função PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME pega uma string e a divide no caractere de ponto. Ele usa um número como segundo argumento e esse número especifica qual segmento da cadeia de caracteres retornar (trabalhando de trás para frente).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
O problema óbvio é quando a string já contém um ponto. Ainda acho que usar uma UDF é a melhor maneira ... outras sugestões?
SPLIT()
função não é fornecida porque incentiva um design de banco de dados ruim, e o banco de dados nunca será otimizado para usar os dados armazenados nesse formato. O RDBMS não é obrigado a ajudar os desenvolvedores a fazer coisas estúpidas que foram projetadas para não manipular. A resposta correta será sempre "Normalize seu banco de dados como dissemos a você 40 anos atrás". Nem o SQL nem o RDBMS são responsáveis pelo design inadequado.
Primeiro, crie uma função (usando CTE, a expressão de tabela comum elimina a necessidade de uma tabela temporária)
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
Em seguida, use-o como qualquer tabela (ou modifique-o para caber no seu proc armazenado existente) como este.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Atualizar
A versão anterior falharia na string de entrada com mais de 4000 caracteres. Esta versão cuida da limitação:
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
O uso permanece o mesmo.
100
(para evitar loop infinito). Use a dica MAXRECURSION para definir o número de níveis de recursão ( 0
para 32767
, 0
é "sem limite" - pode esmagar o servidor). Entre, resposta muito melhor do que PARSENAME
, porque é universal :-). +1
maxrecursion
a esta solução, lembre-se desta pergunta e de suas respostas Como configurar a maxrecursion
opção para um CTE dentro de uma função com valor de tabela .
s
não está mais definido
A maioria das soluções aqui são usadas enquanto loops ou CTEs recursivas. Uma abordagem baseada em conjunto será superior, prometo, se você puder usar um delimitador que não seja um espaço:
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 da amostra:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
Resultados:
----
blat
Você também pode adicionar o idx
argumento desejado à função, mas deixarei isso como um exercício para o leitor.
Você não pode fazer isso apenas com a função nativaSTRING_SPLIT
adicionada no SQL Server 2016, porque não há garantia de que a saída será renderizada na ordem da lista original. Em outras palavras, se você passar 3,6,1
o resultado, provavelmente estará nessa ordem, mas poderia estar 1,3,6
. Pedi a ajuda da comunidade para melhorar a função incorporada aqui:
Com bastante feedback qualitativo , eles podem considerar fazer algumas dessas melhorias:
Mais sobre funções de divisão, por que (e prova disso) enquanto loops e CTEs recursivos não são dimensionados, e melhores alternativas, se as seqüências de caracteres provenientes da camada de aplicação são divididas:
No SQL Server 2016 ou superior, no entanto, você deve observar STRING_SPLIT()
e STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
e a saída produzida foi: Valor Olá ello llo lo o John ohn hn n smith mito om th h
Você pode aproveitar uma tabela Number para fazer a análise de string.
Crie uma tabela 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
Criar tabela de teste com 1000000 linhas
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
Crie a função
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 (gera linhas de 3mil em 40s no meu laptop)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
Limpar
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
O desempenho aqui não é incrível, mas chamar uma função em uma tabela de um milhão de linhas não é a melhor idéia. Se estiver executando uma string dividida em muitas linhas, eu evitaria a função.
desc
fossem removidos?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
@NothingsImpossible foi concluído em 1,5 minutos . @hello_earth Como sua solução se compara em seqüências mais longas com mais de 4 campos?
Esta questão não é sobre uma abordagem de divisão de cadeias , mas sobre como obter o enésimo elemento .
Todas as respostas aqui estão fazendo algum tipo de divisão string usando recursão, CTE
s, múltipla CHARINDEX
, REVERSE
e PATINDEX
, funções inventando, chamada de métodos CLR, tabelas de números, CROSS APPLY
é ... A maioria das respostas cobrir muitas linhas de código.
Mas - se você realmente deseja nada além de uma abordagem para obter o enésimo elemento - isso pode ser feito como uma única linha , sem UDF, nem mesmo como uma sub-seleção ... E como um benefício extra: digite safe
Obtenha a parte 2 delimitada por um espaço:
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)')
Obviamente, você pode usar variáveis para delimitador e posição (use sql:column
para recuperar a posição diretamente do valor de uma 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)')
Se a sua sequência incluir caracteres proibidos (especialmente um entre eles &><
), você ainda poderá fazê-lo dessa maneira. Basta usar FOR XML PATH
sua string primeiro para substituir todos os caracteres proibidos pela seqüência de escape apropriada.
É um caso muito especial se - além disso - seu delimitador for o ponto e vírgula . Nesse caso, substituo o delimitador primeiro por '# DLMT #' e substituo-o pelas tags XML finalmente:
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)');
Lamentavelmente, os desenvolvedores esqueceram de retornar o índice da peça STRING_SPLIT
. Mas, usando o SQL-Server 2016+, há JSON_VALUE
e OPENJSON
.
Com JSON_VALUE
podemos passar na posição como o array do índice.
Para OPENJSON
a documentação afirma claramente:
Quando OPENJSON analisa uma matriz JSON, a função retorna os índices dos elementos no texto JSON como chaves.
A string como 1,2,3
necessidades nada mais do que colchetes: [1,2,3]
.
Uma série de palavras como this is an example
precisa ser ["this","is","an","example"]
.
Essas são operações de cadeia muito fáceis. Apenas tente:
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,']'));
--Veja isto para um separador de cadeia de posição seguro ( baseado em zero ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
Em este post eu testei várias abordagens e encontrado, que OPENJSON
é muito rápido. Até muito mais rápido que o famoso método "delimitedSplit8k ()" ...
Podemos usar uma matriz dentro de uma matriz simplesmente usando o dobro [[]]
. Isso permite uma WITH
cláusula digitada :
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
seções também podem lidar com isso ... Mas depois do elenco elas desaparecem (alteradas para escapadas text()
implicitamente). Eu não gosto de mágica sob o capô , então prefiro a (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
abordagem. Isso parece mais limpo para mim e acontece de qualquer maneira ... (Um pouco mais sobre CDATA e XML ).
Aqui está uma UDF que fará isso. Ele retornará uma tabela com os valores delimitados, ainda não experimentou todos os cenários, mas seu exemplo funciona bem.
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
Você poderia chamar assim:
Select * From SplitString('Hello John Smith',' ')
Edit: Solução atualizada para manipular delimters com len> 1 como em:
select * From SplitString('Hello**John**Smith','**')
Aqui eu posto uma maneira simples de solução
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
Execute a função como esta
select * from dbo.split('Hello John Smith',' ')
Na minha opinião, vocês estão tornando as coisas muito complicadas. Basta criar um CLR UDF e pronto.
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()));
}
};
Que tal usar string
e values()
declaração?
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 alcançado.
id item
1 Hello
2 John
3 Smith
Eu uso a resposta de frederic, mas isso não funcionou no SQL Server 2005
Eu modifiquei e eu estou usando select
com union all
e 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
E o conjunto de resultados é:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
chama implicitamente um procedimento armazenado e você não pode usar procedimentos armazenados em UDFs.
Esse padrão funciona bem e você pode generalizar
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
note FIELD , INDEX e TYPE .
Deixe alguma tabela com identificadores como
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Então, você pode escrever
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 e fundir todas as peças.
Se o seu banco de dados tiver um nível de compatibilidade 130 ou superior, você poderá usar a função STRING_SPLIT junto com as cláusulas OFFSET FETCH para obter o item específico por índice.
Para obter o item no índice N (baseado em zero), você pode usar o seguinte código
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Para verificar o nível de compatibilidade do seu banco de dados , execute este código:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
abordagem baseada em -split, pois ela permite buscar o valor com segurança de tipo e não precisa de uma subconsulta, mas é um bom. +1 do meu lado
STRING_SPLIT
demandas para v2016 +. Nesse caso, é muito melhor usar OPENJSON
ou JSON_VALUE
. Você pode querer verificar minha resposta
Eu estava procurando a solução na net e o abaixo funciona para mim. Ref .
E você chama a função assim:
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
Ainda outra não obtém parte da string pela função delimeter:
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
e o uso:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
que retorna:
c
Tente o seguinte:
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
Teste assim:
select * from SplitWordList('Hello John Smith')
O exemplo a seguir usa um CTE recursivo
Atualização 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
Demonstração no 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
Você pode dividir uma string no SQL sem precisar de uma função:
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);
Se você precisar suportar seqüências de caracteres arbitrárias (com caracteres especiais 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);
Eu sei que é uma pergunta antiga, mas acho que alguém pode se beneficiar da minha solução.
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
Vantagens:
Limitações:
Nota : a solução pode fornecer sub-string até N.
Para superar a limitação, podemos usar a seguinte ref .
Mas, novamente, a solução acima não pode ser usada em uma tabela (não foi possível usá-la).
Mais uma vez, espero que esta solução possa ajudar alguém.
Atualização: No caso de Registros> 50000, não é aconselhável usar, LOOPS
pois isso prejudicará o desempenho
Solução pura baseada em conjunto usando TVF
com recursiva CTE
. Você pode JOIN
e APPLY
esta função para qualquer conjunto de dados.
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
Quase todas as outras respostas estão substituindo a sequência que está sendo dividida, que desperdiça ciclos da CPU e executa alocações de memória desnecessárias.
Cubro uma maneira muito melhor de fazer uma divisão de string aqui: http://www.digitalruby.com/split-string-sql-server/
Aqui está o 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.
Solução CTE recursiva com problemas no servidor, teste-a
Configuração do esquema do 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 |
embora semelhante à resposta baseada em xml de josejuan, eu descobri que o processamento do caminho xml apenas uma vez; em seguida, o giro foi moderadamente mais 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
correu às 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
correu às 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
E USE
select *from dbo.fnSplitString('Querying SQL Server','')
se alguém quiser obter apenas uma parte do texto separado pode usar isso
select * 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
)
Eu desenvolvi isso,
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
a única atenção que você deve ter é o ponto '.' esse fim do @x sempre deve estar lá.
com base na solução @NothingsImpossible, ou melhor, comente a resposta mais votada (logo abaixo da resposta aceita), achei a seguinte solução rápida e suja que atende às minhas próprias necessidades - ela tem o benefício de estar exclusivamente no domínio SQL.
dada uma string "primeiro; segundo; terceiro; quarto; quinto", por exemplo, quero obter o terceiro token. isso funciona apenas se soubermos quantos tokens a string terá - nesse caso, são 5. Portanto, minha maneira de ação é cortar os dois últimos tokens (consulta interna) e depois os dois primeiros tokens ( consulta externa)
Eu sei que isso é feio e cobre as condições específicas em que eu estava, mas estou publicando apenas no caso de alguém achar útil. Felicidades
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 do SQL Server 2016 , string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
não garante devolver o mesmo pedido. Mas OPENJSON
não (ver a minha resposta (seção de atualização) )