Utilizzando SQL Server, come divido una stringa in modo da poter accedere all'elemento x?
Prendi una stringa "Hello John Smith". Come posso dividere la stringa per spazio e accedere all'elemento all'indice 1 che dovrebbe restituire "John"?
Utilizzando SQL Server, come divido una stringa in modo da poter accedere all'elemento x?
Prendi una stringa "Hello John Smith". Come posso dividere la stringa per spazio e accedere all'elemento all'indice 1 che dovrebbe restituire "John"?
Risposte:
È possibile trovare utile la soluzione nella funzione definita dall'utente di SQL per analizzare una stringa delimitata (da The Code Project ).
Puoi usare questa semplice logica:
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 no SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
?
STRING_SPLIT
che divide una stringa e restituisce un risultato di tabella a una colonna che è possibile utilizzare in SELECT
un'istruzione o altrove.
Non credo che SQL Server abbia una funzione split integrata, quindi a parte un UDF, l'unica altra risposta che conosco è dirottare la funzione PARSENAME:
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)
PARSENAME prende una stringa e la divide sul carattere punto. Prende un numero come secondo argomento e quel numero specifica quale segmento della stringa restituire (lavorando da dietro a avanti).
SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3) --return Hello
Il problema evidente è quando la stringa contiene già un punto. Penso ancora che utilizzare un UDF sia il modo migliore ... altri suggerimenti?
SPLIT()
funzione non viene fornita perché incoraggia una progettazione errata del database e il database non sarà mai ottimizzato per utilizzare i dati memorizzati in questo formato. RDBMS non è obbligato ad aiutare gli sviluppatori a fare cose stupide che è stato progettato per non gestire. La risposta corretta sarà sempre "Normalizza il tuo database come ti abbiamo detto 40 anni fa." Né SQL né RDBMS sono responsabili di una progettazione scadente.
Innanzitutto, crea una funzione (usando CTE, l'espressione di tabella comune elimina la necessità di una tabella temporanea)
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
Quindi, usalo come qualsiasi tabella (o modificalo per adattarlo al tuo proc memorizzato esistente) in questo modo.
select s
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Aggiornare
La versione precedente avrebbe esito negativo per una stringa di input più lunga di 4000 caratteri. Questa versione si occupa della limitazione:
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
L'utilizzo rimane lo stesso.
100
(per evitare loop infiniti). Utilizzare l'hint MAXRECURSION per definire il numero di livelli di ricorsione ( 0
a 32767
, 0
è "nessun limite" - può schiacciare il server). A proposito, risposta molto meglio di PARSENAME
, perché è universale :-). +1
maxrecursion
a questa soluzione tenere presente questa domanda e le sue risposte. Come impostare l' maxrecursion
opzione per un CTE all'interno di una funzione con valori di tabella .
s
non è più definito
La maggior parte delle soluzioni qui utilizza mentre loop o CTE ricorsivi. Un approccio basato su set sarà superiore, lo prometto, se puoi usare un delimitatore diverso da uno spazio:
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
);
Esempio di utilizzo:
SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
WHERE idx = 3;
risultati:
----
blat
Puoi anche aggiungere il idx
che vuoi come argomento alla funzione, ma lo lascerò come esercizio al lettore.
Non è possibile farlo con solo la funzione nativaSTRING_SPLIT
aggiunta in SQL Server 2016, poiché non esiste alcuna garanzia che l'output verrà visualizzato nell'ordine dell'elenco originale. In altre parole, se si passa 3,6,1
il risultato sarà probabilmente in quell'ordine, ma potrebbe esserlo 1,3,6
. Ho chiesto l'aiuto della comunità per migliorare la funzione integrata qui:
Con un feedback qualitativo sufficiente , possono effettivamente prendere in considerazione alcuni di questi miglioramenti:
Maggiori informazioni sulle funzioni di divisione, perché (e dimostrarlo) mentre i loop e i CTE ricorsivi non si ridimensionano e alternative migliori se si dividono le stringhe provenienti dal livello dell'applicazione:
In SQL Server 2016 o versioni successive, tuttavia, è necessario esaminare STRING_SPLIT()
e STRING_AGG()
:
select * from DBO.SplitString('Hello John smith', ' ');
e l'output prodotto era: Valore Ciao ello llo lo o John ohn hn n smith mith
È possibile sfruttare una tabella numerica per eseguire l'analisi delle stringhe.
Creare una tabella numerica fisica:
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
Creare una tabella di test con 1000000 righe
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 funzione
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
Utilizzo (emette 3mil file in 40 secondi sul mio laptop)
select *
from #yak
cross apply dbo.ufn_ParseArray(array, ',', 1)
pulire
drop table dbo.Numbers;
drop function [dbo].[ufn_ParseArray]
Le prestazioni qui non sono eccezionali, ma chiamare una funzione su un milione di righe non è la migliore idea. Se eseguissi una stringa divisa su più file, eviterei la funzione.
desc
rimossi?
REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
da @NothingsImpossible è stato completato in 1,5 minuti. @hello_earth Come si confronta la tua soluzione su stringhe più lunghe con più di 4 campi?
Questa domanda non riguarda un approccio con suddivisione in stringhe , ma come ottenere l'ennesimo elemento .
Tutte le risposte qui stanno facendo una specie di divisione delle stringhe usando ricorsione, funzioni CTE
multiple CHARINDEX
, REVERSE
e PATINDEX
inventando, chiamata per metodi CLR, tabelle di numeri, CROSS APPLY
... La maggior parte delle risposte copre molte righe di codice.
Ma - se davvero non vuoi altro che un approccio per ottenere l'ennesimo elemento - questo può essere fatto come un vero one-liner , senza UDF, nemmeno come sottoselezione ... E come vantaggio extra: digita sicuro
Ottieni la parte 2 delimitata da uno spazio:
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)')
Ovviamente puoi usare le variabili per delimitatore e posizione (usasql:column
per recuperare la posizione direttamente dal valore di una query):
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 la tua stringa potrebbe includere caratteri proibiti (in particolare uno tra &><
), puoi comunque farlo in questo modo. Basta usare FOR XML PATH
prima la stringa per sostituire implicitamente tutti i caratteri proibiti con la sequenza di escape adatta.
È un caso molto speciale se, inoltre, il delimitatore è il punto e virgola . In questo caso sostituisco prima il delimitatore a '# DLMT #' e infine lo sostituisco con i tag 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)');
Purtroppo gli sviluppatori hanno dimenticato di restituire l'indice della parte con STRING_SPLIT
. Ma, usando SQL-Server 2016+, c'è JSON_VALUE
e OPENJSON
.
Con JSON_VALUE
possiamo passare nella posizione come array dell'indice.
Per OPENJSON
la documentazione afferma chiaramente:
Quando OPENJSON analizza un array JSON, la funzione restituisce gli indici degli elementi nel testo JSON come chiavi.
Una stringa come 1,2,3
esigenze niente di più che le staffe: [1,2,3]
.
Una stringa di parole come this is an example
deve essere ["this","is","an","example"]
.
Queste sono operazioni di stringa molto semplici. Provalo e basta:
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,']'));
--Vedi questo per uno splitter di stringa di posizione sicuro ( a base zero ):
SELECT JsonArray.[key] AS [Position]
,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray
In questo post ho testato vari approcci e ho scoperto che OPENJSON
è molto veloce. Anche molto più veloce del famoso metodo "delimitedSplit8k ()" ...
Possiamo usare un array all'interno di un array semplicemente usando il doppio [[]]
. Ciò consente una WITH
clausola tipizzata :
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
sezioni possono occuparsi di questo ... Ma dopo il cast se ne sono andati (sono cambiati in modo text()
implicito per sfuggire ). Non mi piace la magia sotto il cofano , quindi preferirei l ' (SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
approccio. Mi sembra più pulito e succede comunque ... (Qualche informazione in più su CDATA e XML ).
Ecco un UDF che lo farà. Restituirà una tabella dei valori delimitati, non ci ho provato tutti gli scenari ma il tuo esempio funziona bene.
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 chiameresti così:
Select * From SplitString('Hello John Smith',' ')
Modifica: soluzione aggiornata per gestire i delimitatori con un len> 1 come in:
select * From SplitString('Hello**John**Smith','**')
Qui inserisco un modo semplice di soluzione
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
Eseguire la funzione in questo modo
select * from dbo.split('Hello John Smith',' ')
Secondo me voi ragazzi lo state rendendo troppo complicato. Basta creare un CLR UDF e finirlo.
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()));
}
};
Che dire di usare string
e values()
dichiarazione?
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
Set di risultati raggiunto.
id item
1 Hello
2 John
3 Smith
Uso la risposta di frederic ma questo non ha funzionato in SQL Server 2005
Ho modificato e sto usando select
con union all
e funziona
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 il set di risultati è:
id item
1 Hello
2 John
3 Smith
4 how
5 are
6 you
EXEC
. EXEC
chiama implicitamente una stored procedure e non è possibile utilizzare le stored procedure negli UDF.
Questo modello funziona bene e puoi generalizzare
Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
^^^^^ ^^^^^ ^^^^
nota CAMPO , INDICE e TIPO .
Lascia una tabella con identificatori come
sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Quindi, puoi scrivere
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
scissione e fusione di tutte le parti.
Se il tuo database ha un livello di compatibilità di 130 o superiore, puoi utilizzare la funzione STRING_SPLIT insieme a OFFSET FETCH clausole per ottenere l'elemento specifico per indice.
Per ottenere l'elemento nell'indice N (basato su zero), è possibile utilizzare il seguente codice
SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Per verificare il livello di compatibilità del database , eseguire questo codice:
SELECT compatibility_level
FROM sys.databases WHERE name = 'YourDBName';
xml
approccio basato su -split, in quanto consente di recuperare il valore sicuro e non necessita di una query secondaria, ma si tratta di un buono +1 dalla mia parte
STRING_SPLIT
richieste per v2016 +. In questo caso è molto meglio usare OPENJSON
o JSON_VALUE
. Potresti voler controllare la mia risposta
Stavo cercando la soluzione su rete e il seguito funziona per me. arbitro .
E chiamate la funzione in questo modo:
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
Ancora un altro ottiene l'ennesima parte della stringa con la funzione delimitatore:
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 l'uso:
select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
che ritorna:
c
Prova questo:
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
Provalo in questo modo:
select * from SplitWordList('Hello John Smith')
L'esempio seguente utilizza un CTE ricorsivo
Aggiornamento 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
Demo su 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
È possibile dividere una stringa in SQL senza la necessità di una funzione:
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 è necessario supportare stringhe arbitrarie (con caratteri speciali 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);
So che è una vecchia domanda, ma penso che qualcuno possa beneficiare della mia soluzione.
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
vantaggi:
limitazioni:
Nota : la soluzione può assegnare una sottostringa fino a N.
Per superare la limitazione possiamo usare il seguente rif .
Ma ancora una volta la soluzione sopra non può essere utilizzata in una tabella (Actaully non sono stata in grado di usarla).
Ancora una volta spero che questa soluzione possa aiutare qualcuno.
Aggiornamento: in caso di record> 50000 non è consigliabile utilizzarlo LOOPS
poiché peggiorerà le prestazioni
Pura soluzione basata su set usando TVF
con ricorsivo CTE
. È possibile JOIN
e APPLY
questa funzione per qualsiasi set di dati.
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;
Risultato:
value index
-------------
John 1
Quasi tutte le altre risposte stanno sostituendo la stringa da dividere che spreca cicli di CPU ed esegue allocazioni di memoria non necessarie.
Copro un modo molto migliore per fare una divisione di stringhe qui: http://www.digitalruby.com/split-string-sql-server/
Ecco il codice:
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.
Soluzione CTE ricorsiva con dolore del server, testala
Installazione schema MS SQL Server 2008 :
create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');
Query 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 |
sebbene simile alla risposta basata su XML di josejuan, ho scoperto che l'elaborazione del percorso XML solo una volta, quindi il pivot era moderatamente più efficiente:
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
corse alle 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
corse alle 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 USARLO
select *from dbo.fnSplitString('Querying SQL Server','')
se qualcuno vuole ottenere solo una parte del testo separato può utilizzarlo
seleziona * da 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
)
Ho sviluppato questo,
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
l'unica attenzione che dovresti è puntare '.' quella fine di @x è sempre dovrebbe essere lì.
basandomi sulla soluzione @NothingsImpossible o, piuttosto, commentando la risposta più votata (appena sotto quella accettata), ho trovato la seguente procedura rapida soddisfa le mie esigenze - ha il vantaggio di trovarsi esclusivamente all'interno del dominio SQL.
data una stringa "primo; secondo; terzo; quarto; quinto", diciamo, voglio ottenere il terzo token. funziona solo se sappiamo quanti token avrà la stringa - in questo caso sono 5. quindi il mio modo di agire è di tagliare via gli ultimi due token (query interna) e quindi di tagliare i primi due token ( query esterna)
so che è brutto e copre le condizioni specifiche in cui mi trovavo, ma lo sto pubblicando nel caso in cui qualcuno lo trovasse utile. Saluti
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 partire da SQL Server 2016 abbiamo string_split
DECLARE @string varchar(100) = 'Richard, Mike, Mark'
SELECT value FROM string_split(@string, ',')
STRING_SPLIT
non garantisce la restituzione dello stesso ordine. Ma OPENJSON
(vedi la mia risposta (sezione aggiornamento) )