Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Usando T-SQL, retorne o enésimo elemento delimitado de uma string


Esta é a resposta mais fácil para recuperar o 67 (tipo seguro!! ):
SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int')

A seguir, você encontrará exemplos de como usar isso com variáveis ​​para a string, o delimitador e a posição (mesmo para casos de borda com caracteres proibidos por XML)

A mais fácil


Esta pergunta não é sobre uma abordagem de divisão de strings , mas sobre como obter o enésimo elemento . A maneira mais fácil e totalmente inline seria esta IMO:

Este é um verdadeiro one-liner para obter 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)')

Variáveis ​​podem ser usadas com sql:variable() ou sql:column()


Claro que 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)')

Edge-Case com caracteres proibidos por XML


Se sua string pode incluir caracteres proibidos , você ainda pode fazê-lo desta forma. Basta usar FOR XML PATH em sua string primeiro para substituir todos os caracteres proibidos pela sequência de escape apropriada implicitamente.

É um caso muito especial se - adicionalmente - seu delimitador for o ponto e vírgula . Nesse caso, substituo o delimitador primeiro por '#DLMT#' e substituo isso 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)');

ATUALIZAÇÃO para SQL-Server 2016+


Lamentavelmente, os desenvolvedores esqueceram de retornar o índice da peça com STRING_SPLIT . Mas, usando o SQL-Server 2016+, há JSON_VALUE e OPENJSON .

Com JSON_VALUE podemos passar a posição como o array do índice.

Para OPENJSON a documentação diz claramente:

Quando OPENJSON analisa uma matriz JSON, a função retorna os índices dos elementos no texto JSON como chaves.

Uma string como 1,2,3 não precisa mais do que colchetes:[1,2,3] .
Uma sequência de palavras como this is an example precisa ser ["this","is","an"," example"] .
Estas são operações de string muito fáceis. Basta experimentar:
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 divisor de string seguro de posição (baseado em zero ):
SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

Neste post eu testei várias abordagens e descobri que OPENJSON é realmente rápido. Muito mais rápido que o famoso método "delimitedSplit8k()"...

ATUALIZAÇÃO 2 - Obtenha os valores com segurança de tipo


Podemos usar um array dentro de um array simplesmente usando [[]] duplicado . Isso permite um WITH digitado -cláusula:
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