Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Usando T-SQL, restituisci l'ennesimo elemento delimitato da una stringa

Questa è la risposta più semplice per recuperare il 67 (type-safe!! ):

SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int')

Di seguito troverai esempi su come usarlo con le variabili per la stringa, il delimitatore e la posizione (anche per i casi limite con caratteri vietati da XML)

Quello facile

Questa domanda non riguarda un approccio di divisione delle stringhe , ma su come ottenere l'ennesimo elemento . Il modo più semplice e completamente inlineabile sarebbe questo IMO:

Questa è una vera battuta per ottenere 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)')

Le variabili possono essere utilizzate con sql:variable() o sql:column()

Ovviamente puoi usare le variabili per delimitatore e posizione (usa sql: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)')

Edge-Case con caratteri non consentiti da XML

Se la tua stringa potrebbe includere caratteri non consentiti , puoi ancora farlo in questo modo. Usa semplicemente FOR XML PATH prima sulla stringa per sostituire implicitamente tutti i caratteri vietati con la sequenza di escape adatta.

È un caso molto speciale se, inoltre, il tuo delimitatore è il punto e virgola . In questo caso sostituisco prima il delimitatore con '#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)');

AGGIORNAMENTO per SQL-Server 2016+

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 la 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 non ha bisogno di altro che parentesi:[1,2,3] .
Una stringa di parole come this is an example deve essere ["this","is","an"," example"] .
Si tratta di operazioni sulle stringhe molto semplici. Provalo:

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 un divisore di stringhe sicuro per la posizione (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 è davvero veloce. Anche molto più veloce del famoso metodo "delimitedSplit8k()"...

UPDATE 2 - Ottieni i valori type-safe

Possiamo usare un array all'interno di un array semplicemente usando il doppio [[]] . Ciò consente un WITH digitato -clausola:

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