Stringa divisa T-SQL


139

Ho una colonna di SQL Server 2008 R2 contenente una stringa che devo dividere per una virgola. Ho visto molte risposte su StackOverflow ma nessuna di esse funziona in R2. Mi sono assicurato di avere autorizzazioni selezionate per qualsiasi esempio di funzione divisa. Qualsiasi aiuto è molto apprezzato.


7
Questo è uno dei milioni di risposte che mi piace stackoverflow.com/a/1846561/227755
Nurettin

2
Cosa intendi con "nessuno di loro funziona"? Può essere più preciso?
Aaron Bertrand,

Andy mi ha indicato la giusta direzione mentre stavo eseguendo la funzione in modo errato. Questo è il motivo per cui nessuna delle altre risposte dello stack ha funzionato. Colpa mia.
Lee Grindon,

2
possibile duplicato della stringa di divisione in SQL
Luv

C'è una mdq.RegexSplitfunzione nel componente aggiuntivo "Master Data Services", che può essere d'aiuto. Sicuramente vale la pena indagare .
jpaugh

Risposte:


233

Ho usato questo SQL prima del quale potrebbe funzionare per te: -

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

e per usarlo: -

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')

1
Bello, questo è esattamente quello che stavo cercando grazie
Lee Grindon,

2
Grazie mille Andy. Ho apportato un piccolo miglioramento al tuo script per consentire alla funzione di restituire un elemento in un indice specifico nella stringa divisa. È utile solo in situazioni in cui si sta analizzando la struttura della colonna. gist.github.com/klimaye/8147193
CF_Maintainer

1
Ho pubblicato alcuni miglioramenti (con casi di test di supporto) sulla mia pagina github qui . Lo
posterò

8
Anche se questa è un'ottima risposta, è obsoleta ... Gli approcci procedurali (in particolare i loop) sono qualcosa da evitare ... Vale la pena di cercare risposte più recenti ...
Shnugo,

2
Sono totalmente d'accordo con @Shnugo. Gli splitter in loop funzionano ma orribilmente lenti. Qualcosa di simile a questo sqlservercentral.com/articles/Tally+Table/72993 è molto meglio. Alcune altre eccellenti opzioni basate sul set possono essere trovate qui. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

61

Invece di CTE ricorsivi e mentre loop, qualcuno ha preso in considerazione un approccio più set-based? Si noti che questa funzione è stata scritta per la domanda, basata su SQL Server 2008 e virgola come delimitatore . In SQL Server 2016 e versioni successive (e nel livello di compatibilità 130 e versioni successive), STRING_SPLIT()è un'opzione migliore .

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(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_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

Se si desidera evitare che la lunghezza della stringa sia <= il numero di righe in sys.all_columns(9.980 in modelin SQL Server 2017; molto più elevato nei propri database utente), è possibile utilizzare altri approcci per derivare i numeri, come costruendo la propria tabella dei numeri . È inoltre possibile utilizzare un CTE ricorsivo nei casi in cui non è possibile utilizzare le tabelle di sistema o creare il proprio:

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

Ma dovrai aggiungere OPTION (MAXRECURSION 0)(o MAXRECURSION <longest possible string length if < 32768>) alla query esterna per evitare errori con ricorsione per stringhe> 100 caratteri. Se anche questa non è una buona alternativa, vedi questa risposta come sottolineato nei commenti.

(Inoltre, il delimitatore dovrà essere NCHAR(<=1228). Ancora alla ricerca del perché.)

Maggiori informazioni sulle funzioni split, 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:


1
C'è un piccolo bug in questa procedura per il caso in cui ci sarebbe un valore nullo alla fine della stringa - come in "1,2,, 4," - poiché il valore finale non viene analizzato. Per correggere questo errore, l'espressione "WHERE Number <= LEN (@List)" deve essere sostituita con "WHERE Number <= LEN (@List) + 1".
SylvainL,

@SylvainL Immagino che dipenda dal comportamento che desideri. Nella mia esperienza, molte persone vogliono ignorare le virgole finali in quanto non rappresentano realmente un elemento reale (quante copie di una stringa vuota sono necessarie)? Ad ogni modo, il vero modo per farlo - se seguirai il secondo link - è quello di fare un casino con la divisione di grosse stringhe brutte nel lento T-SQL.
Aaron Bertrand,

1
Come hai detto, la maggior parte delle persone vuole ignorare le virgole finali ma purtroppo, non tutte. Suppongo che una soluzione più completa sarebbe quella di aggiungere un parametro per specificare cosa fare in questo caso, ma il mio commento è solo una piccola nota per assicurarsi che nessuno si dimentichi di questa possibilità, poiché in molti casi può essere del tutto reale.
SylvainL,

Ho un comportamento strano con quella funzione. Se uso direttamente una stringa come parametro, funziona. Se ho un varchar, non lo è. Puoi riprodurre facilmente: dichiara invarchar come varchar set invarchar = 'ta; aa; qq' SELEZIONA Valore da [dbo]. [SplitString] (invarchar, ';') SELEZIONA Valore da [dbo]. [SplitString] ('ta; aa; qq ','; ')
Patrick Desjardins,

Mi piace questo approccio, ma se il numero di oggetti restituiti da sys.all_objectsè inferiore al numero dei caratteri nella stringa di input, troncherà la stringa e i valori andranno persi. Poiché sys.all_objectsviene utilizzato solo come un po 'di un trucco per generare righe, allora ci sono modi migliori per farlo, ad esempio questa risposta .
nocche

56

Finalmente l'attesa è finita in SQL Server 2016 hanno introdotto la funzione di stringa divisa:STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

Tutti gli altri metodi per dividere stringhe come XML, tabella Tally, mentre loop, ecc. Sono stati spazzati via da questa STRING_SPLITfunzione.

Ecco un eccellente articolo con confronto delle prestazioni: Sorprese e ipotesi sulle prestazioni: STRING_SPLIT


5
ovviamente risponde alla domanda su come dividere la stringa per quelli con server aggiornati, ma quelli di noi ancora bloccati su 2008 / 2008R2, dovranno andare con una delle altre risposte qui.
mpag,

2
Devi dare un'occhiata al livello di compatibilità nel tuo database. Se è inferiore a 130 non sarà possibile utilizzare la funzione STRING_SPLIT.
Luis Teijon,

In realtà, se la compatibilità non è 130 e si esegue 2016 (o Azure SQL) è possibile impostare la compatibilità fino a 130 usando: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Michieal

23

Il modo più semplice per farlo è utilizzando il XMLformato.

1. Conversione di stringhe in righe senza tabella

QUERY

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

RISULTATO

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. Conversione in righe da una tabella con un ID per ogni riga CSV

TABELLA SORGENTE

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

QUERY

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

RISULTATO

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x

Questo approccio si interromperà se @Stringcontiene caratteri proibiti ... Ho appena pubblicato una risposta per superare questo problema.
Shnugo,

9

Avevo bisogno di un modo rapido per sbarazzarmi di +4un codice postale .

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

No proc ... no UDF ... solo un piccolo comando inline stretto che fa quello che deve. Non elegante, non elegante.

Cambia il delimitatore secondo necessità, ecc. E funzionerà per qualsiasi cosa.


4
Non è di questo che si tratta. L'OP ha un valore come '234.542,23' e vogliono dividerlo in tre file ... 1a riga: 234, 2a riga: 542, 3a fila: 23. È una cosa complicata da fare in SQL.
codeulike,

7

se sostituisci

WHILE CHARINDEX(',', @stringToSplit) > 0

con

WHILE LEN(@stringToSplit) > 0

puoi eliminare l'ultimo inserto dopo il ciclo while!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END

Ciò comporterebbe il troncamento dell'ultimo carattere dell'ultimo elemento. cioè "AL, AL" diventerebbe "AL" | "A", ad esempio "ABC, ABC, ABC" diventerebbe "ABC" | "ABC" | "AB"
Sviluppatore Microsoft il

l'aggiunta +1di SELECT @pos = LEN(@stringToSplit)sembra che risolva il problema. Tuttavia, SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)verrà restituito a Invalid length parameter passed to the LEFT or SUBSTRING functionmeno che non si aggiunga anche +1al terzo parametro di SUBSTRING. oppure potresti sostituire quell'incarico conSET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag il

1
Ho pubblicato alcuni miglioramenti (con casi di test di supporto) sulla mia pagina github qui . Lo
posterò

Anch'io ho notato la questione evidenziata da Terry sopra. Ma la logica data da @AviG è così interessante che non fallisce nel mezzo per un lungo elenco di token. Prova questa chiamata di prova per verificare (questa chiamata dovrebbe restituire 969 token) selezionare * da dbo.splitstring ('token1, token2 ,, to, 9, token969') Quindi ho provato il codice fornito da mpag per controllare i risultati per lo stesso chiama sopra e trovato che può restituire solo 365 token. Alla fine ho corretto il codice di AviG sopra e pubblicato la funzione bug free come nuova risposta di seguito poiché il commento qui consente solo un testo limitato. Controlla la risposta sotto il mio nome per provarlo.
Gemunu R Wickremasinghe il

3

Tutte le funzioni per la suddivisione delle stringhe che usano un qualche tipo di Loop-ing (iterazioni) hanno prestazioni scadenti. Dovrebbero essere sostituiti con una soluzione basata su set.

Questo codice viene eseguito in modo eccellente.

CREATE FUNCTION dbo.SplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO

Questo approccio si interromperà se @Listcontiene caratteri proibiti ... Ho appena pubblicato una risposta per superare questo problema.
Shnugo,

Sto votando la tua risposta perché il tuo lavora con lo spazio come delimitatore e il più votato non lo fa
KMC

3

L'approccio spesso usato con elementi XML si rompe in caso di caratteri proibiti. Questo è un approccio per usare questo metodo con qualsiasi tipo di carattere, anche con il punto e virgola come delimitatore.

Il trucco è il primo da usare SELECT SomeString AS [*] FOR XML PATH('')per far fuggire tutti i personaggi proibiti in modo corretto. Questo è il motivo, perché sostituisco il delimitatore a un valore magico per evitare problemi con ;come delimitatore.

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

Il risultato

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F

2

Ho dovuto scrivere qualcosa del genere di recente. Ecco la soluzione che mi è venuta in mente. È generalizzato per qualsiasi stringa delimitatore e penso che avrebbe prestazioni leggermente migliori:

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END

1

Una soluzione che utilizza un CTE, se qualcuno dovesse averne bisogno (a parte me, che ovviamente lo ha fatto, ecco perché l'ho scritto).

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit

1

Questo è più su misura. Quando lo faccio, di solito ho un elenco delimitato da virgole di ID univoci (INT o BIGINT), che voglio eseguire il cast come tabella da utilizzare come join interno a un'altra tabella che ha una chiave primaria di INT o BIGINT. Voglio che venga restituita una funzione con valori di tabella in linea in modo da avere il join più efficiente possibile.

L'uso del campione sarebbe:

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

Ho rubato l'idea da http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html , cambiandola per essere valutata in tabella in linea e lanciata come INT.

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO

1

C'è una versione corretta qui, ma ho pensato che sarebbe bello aggiungere una piccola tolleranza d'errore nel caso in cui abbiano una virgola finale e renderla così che tu possa usarla non come una funzione ma come parte di un pezzo di codice più grande . Nel caso in cui lo stai usando solo una volta e non hai bisogno di una funzione. Questo vale anche per gli interi (che è ciò di cui avevo bisogno), quindi potresti dover modificare i tipi di dati.

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs

se dovessi farlo SET @StringToSeperate = @StringToSeperate+','immediatamente prima del WHILEciclo, penso che potresti essere in grado di eliminare il blocco "aggiungi ultimo valore". Vedi anche il mio sol'n su github
mpag il

Su quale risposta si basa? Ci sono molte risposte qui, ed è un po 'confuso. Grazie.
jpaugh

1

Ho modificato un po 'la funzione di + Andy Robinson. Ora puoi selezionare solo la parte richiesta dalla tabella di ritorno:

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5


1

Se hai bisogno di una rapida soluzione ad-hoc per casi comuni con codice minimo, allora questo CTE a due linee ricorsivo lo farà:

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

Utilizzalo come un'istruzione autonoma o aggiungi semplicemente i CTE di cui sopra a una qualsiasi delle tue query e sarai in grado di unire la tabella risultante bcon altre persone per utilizzarle in altre espressioni.

modifica (di Shnugo)

Se aggiungi un contatore, otterrai un indice di posizione insieme all'elenco:

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

Il risultato:

n   s
1   1
2   2333
3   344
4   4

Mi piace questo approccio. Spero non ti dispiaccia, che ho aggiunto qualche miglioramento direttamente nella tua risposta.
Sentiti

1

Prendo il percorso xml avvolgendo i valori in elementi (M ma tutto funziona):

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)

0

ecco una versione che può essere divisa su uno schema usando patindex, un semplice adattamento del post sopra. Ho avuto un caso in cui dovevo dividere una stringa che conteneva più caratteri di separazione.


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

il risultato è simile a questo

stringa stringb x y z


0

Personnaly Uso questa funzione:

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)

0

Ho sviluppato un doppio splitter (prende due personaggi divisi) come richiesto qui . Potrebbe essere di qualche valore in questo thread visto che è il più referenziato per le query relative alla suddivisione delle stringhe.

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

Uso:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

Utilizzo possibile (ottieni il secondo valore di ogni divisione):

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2

0

Ecco un esempio che puoi usare come funzione o anche puoi mettere la stessa logica in procedura. --SELECT * da [dbo] .fn_SplitString;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;

Questo approccio si interromperà se @vCSVcontiene caratteri proibiti ... Ho appena pubblicato una risposta per superare questo problema.
Shnugo,

0

Una soluzione ricorsiva basata su cte

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;

0

Questo si basa sulla risposta di Andy Robertson, avevo bisogno di un delimitatore diverso dalla virgola.

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

E per usarlo:

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(Testato su SQL Server 2008 R2)

EDIT: codice di test corretto


0

/ *

Risposta alla stringa divisa T-SQL
Sulla base delle risposte di Andy Robinson e AviG
Funzionalità avanzata ref: funzione LEN che non include spazi finali in SQL Server
Questo 'file' dovrebbe essere valido sia come file markdown che come file SQL


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

Casi di prova: vedi l'URL indicato come "funzionalità avanzata" sopra

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

* /


eseguito il rollback per onorare "Questo 'file' dovrebbe essere valido sia come file
markdown che

-1
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END

-1

Puoi usare questa funzione:

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO

-1

Con tutto il rispetto per @AviG questa è la versione senza errori della funzione da lui deviata per restituire tutti i token in pieno.

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END

-3

La via più facile:

  1. Installa SQL Server 2016
  2. Usa STRING_SPLIT https://msdn.microsoft.com/en-us/library/mt684588.aspx

Funziona anche in edizione espressa :).


Non dimenticare di impostare "Livello di compatibilità" su SQL Server 2016 (130): in Management Studio, fai clic con il pulsante destro del mouse su database, proprietà / opzioni / livello di compatibilità.
Tomino,

1
Il post originale diceva per SQL 2008 R2. L'installazione di SQL 2016 potrebbe non essere un'opzione
Shawn Gavett,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.