Come divido una stringa in modo da poter accedere all'elemento x?


493

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"?




4
Le risposte più alte qui sono - almeno per me - piuttosto antiquate e piuttosto obsolete. Localizzazione procedurale, loop, ricorsioni, CLR, funzioni, molte righe di codice ... Potrebbe essere interessante leggere le risposte "attive" per trovare approcci più aggiornati .
Shnugo,

Ho aggiunto una nuova risposta con più up-to-date approccio: stackoverflow.com/a/49669994/632604
Gorgi Rankovski

Prova Ottieni l'ennesimo elemento di un elenco -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
José Diz

Risposte:


191

È 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

1
perché 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)?
Beth,

12
@GateKiller Questa soluzione non supporta Unicode e utilizza un codice numerico (18,3) che non lo rende una funzione "riutilizzabile" praticabile.
Filip De Vos,

4
Funziona ma alloca molta memoria e spreca CPU.
jjxtra,

2
A partire da SQL Server 2016, ora esiste una funzione integrata STRING_SPLITche divide una stringa e restituisce un risultato di tabella a una colonna che è possibile utilizzare in SELECTun'istruzione o altrove.
qJake

Peccato che i ragazzi per cui lavoro non sono del 2016. Ma lo terrò a mente nel caso in cui riuscissero a togliersi il vantaggio dalle scarpe. Ottima soluzione nel frattempo. L'ho implementato come funzione e ho aggiunto delimitatore come argomento.
Brandon Griffin,

355

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?


102
Grazie Saul ... Devo sottolineare che questa soluzione è davvero una cattiva soluzione per un vero sviluppo. PARSENAME prevede solo quattro parti, quindi l'utilizzo di una stringa con più di quattro parti provoca la restituzione di NULL. Le soluzioni UDF sono ovviamente migliori.
Nathan Bedford,

33
Questo è un grande trucco, e mi fa anche piangere che qualcosa di simile è necessario per qualcosa di così semplice in lingue reali.
Factor Mystic,

36
Per far funzionare gli indici nel modo "giusto", ovvero a partire da 1, ho dirottato il tuo dirottamento con REVERSE: REVERSE (PARSENAME (REPLACE (REVERSE ('Hello John Smith'), '', '.') , 1)) - Restituisce Hello
NothingsImpossible

3
@FactorMystic First Normal Form richiede di non inserire più valori in un singolo campo. È letteralmente la prima regola di un RDBMS. Una 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.
Pezzi di pancetta

8
@BaconBits mentre concordo in teoria, in pratica strumenti come questo sono utili quando si normalizza un design scadente prodotto da qualcuno che è venuto prima di te.
Tim Abell,

110

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.


14
È elegante ma funziona solo per 100 elementi a causa del limite della profondità di ricorsione.
Pking

4
@Pking, no, il valore predefinito è 100(per evitare loop infiniti). Utilizzare l'hint MAXRECURSION per definire il numero di livelli di ricorsione ( 0a 32767, 0è "nessun limite" - può schiacciare il server). A proposito, risposta molto meglio di PARSENAME, perché è universale :-). +1
Michał Powaga

Aggiungendo maxrecursiona questa soluzione tenere presente questa domanda e le sue risposte. Come impostare l' maxrecursionopzione per un CTE all'interno di una funzione con valori di tabella .
Michał Powaga,

In particolare, fai riferimento alla risposta di Crisfole : il suo metodo la rallenta un po ', ma è più semplice della maggior parte delle altre opzioni.
AHiggins,

punto secondario ma l'utilizzo non rimane lo stesso perché hai cambiato il nome della colonna, quindi snon è più definito
Tim Abell,

62

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,1il 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():


1
Migliore risposta, IMHO. In alcune altre risposte c'è il problema del limite di ricorsione SQL di 100, ma non in questo caso. Implementazione molto veloce e molto semplice. Dov'è il pulsante +2?
T-moty,

5
Ho provato questa funzione alla lettera con l'uso: select * from DBO.SplitString('Hello John smith', ' ');e l'output prodotto era: Valore Ciao ello llo lo o John ohn hn n smith mith
thith

2
@AaronBertrand Il problema originale posto da GateKiller riguarda un delimitatore di spazio.
domenica

1
@ user1255933 Indirizzato.
Aaron Bertrand il

1
@Michael Sì, è vero. Inoltre non avresti una tabella da selezionare se non avessi l'autorizzazione ALTER SCHEMA e non saresti in grado di selezionare da essa se non disponi dell'autorizzazione SELECT Puoi sempre chiedere a qualcuno di creare la funzione per te . O crearlo da qualche parte è possibile crearlo (anche temporaneamente, diciamo in tempdb). E nel 2016+ dovresti usare STRING_SPLIT () e non una funzione che devi comunque creare.
Aaron Bertrand,

38

È 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.


2
La migliore soluzione IMO, le altre hanno qualche tipo di limitazione .. questa è veloce e può analizzare lunghe stringhe con molti elementi.
Pking

Perché ordini n in ordine decrescente? Se ci fossero tre elementi e abbiamo iniziato a numerare da 1, il primo sarà il numero 3 e l'ultimo sarà il numero 1. Non darebbe risultati più intuitivi se descrimossi?
ascia di guerra - fatto con SOverflow il

1
D'accordo, sarebbe più intuitivo nella direzione asc. Stavo seguendo la convenzione di parsename () che utilizza desc
Nathan Skerl il

3
alcune spiegazioni su come questo sarebbe fantastico
Tim Abell,

In un test su 100 milioni di righe fino a 3 campi da analizzare, ufn_ParseArray non è terminato dopo 25 minuti, mentre 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?
wwmbes,

32

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 CTEmultiple CHARINDEX, REVERSEe PATINDEXinventando, 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 PATHprima 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)');

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_VALUEe OPENJSON.

Con JSON_VALUEpossiamo passare nella posizione come array dell'indice.

Per OPENJSONla 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,3esigenze niente di più che le staffe: [1,2,3].
Una stringa di parole come this is an exampledeve 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 ()" ...

AGGIORNAMENTO 2 - Ottieni valori sicuri

Possiamo usare un array all'interno di un array semplicemente usando il doppio [[]]. Ciò consente una WITHclausola 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

Ri: se la tua stringa potrebbe includere caratteri proibiti ... potresti semplicemente avvolgere le sottostringhe in questo modo <x><![CDATA[x<&>x]]></x>.
Salman A

@SalmanA, sì, anche le CDATAsezioni 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 ).
Shnugo,

22

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','**')

Non ha funzionato per select * da dbo.ethos_SplitString_fn ('guy, wicks, was here', ',') id part ----------- ------------ -------------------------------------- 1 ragazzo 2 stoppino
Guy

2
fai attenzione con len () in quanto non restituirà il numero corretto se il suo argomento ha spazi finali., ad esempio len ('-') = 2.
Rory

Non funziona su: seleziona * da dbo.SplitString ('foo, foo test ,,,, foo', ',')
cbp

1
Correzione per cbp .. Seleziona @myString = sottostringa (@ mystring, @ iSpaces + len (@deliminator), len (@myString) - charindex (@ deliminator, @ myString, 0))
Alxwest

16

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',' ')

Mi è piaciuta questa soluzione. Espanso per restituire un valore scalare basato sulla colonna specificata nei risultati.
Alan,

Mi sono bruciato con un '&' nella stringa da dividere usando questo
KeithL

10

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()));
  }
};

20
Immagino che questo sia troppo complicato, perché ho bisogno di avere Visual Studio, quindi abilitare CLR sul server, quindi creare e compilare il progetto e infine aggiungere gli assembly al database, per poterlo utilizzare. Ma è ancora una risposta interessante.
Guillermo Gutiérrez,

3
@ guillegr123, non deve essere complicato. Puoi semplicemente scaricare e installare (gratuitamente!), SQL #, che è una libreria di funzioni e proc SQLCLR. Puoi ottenerlo da SQLsharp.com . Sì, sono l'autore ma String_Split è incluso nella versione gratuita.
Solomon Rutzky,

10

Che dire di usare stringe 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

1
ho usato la tua risposta ma non ha funzionato, ma ho modificato e questo ha funzionato con unione tutto, sto usando sql 2005
angelo

9

Uso la risposta di frederic ma questo non ha funzionato in SQL Server 2005

Ho modificato e sto usando selectcon union alle 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

Questo è davvero fantastico che abbia mai visto in roba sql, ha funzionato per il mio lavoro e lo apprezzo, grazie!
Abdurrahman I.

Mi sono davvero emozionato quando l'ho visto perché sembrava così pulito e facile da capire, ma sfortunatamente non puoi inserirlo in un UDF a causa del EXEC. EXECchiama implicitamente una stored procedure e non è possibile utilizzare le stored procedure negli UDF.
Kristen Hammack,

Funziona perfettamente !! stavo cercando di utilizzare una funzione (SplitStrings_Moden) da qui: sqlperformance.com/2012/07/t-sql-queries/split-strings#comments che fa questo e ci sono voluti un minuto e mezzo per dividere i dati e restituire le righe quando si usano solo 4 numeri di conto. Ho testato la tua versione con un join sinistro sul tavolo con i dati sui numeri di account e ci sono voluti 2 o 3 secondi! Enorme differenza e funziona perfettamente! Darei 20 voti se possibile!
MattE

8

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.


Questa è l'unica soluzione che ti consente di eseguire il cast su tipi specifici ed è moderatamente efficiente (CLR è ancora più efficiente, ma questo approccio gestisce una tabella di righe da 8 GB, 10 token, 10 M in circa 9 minuti (server aws m3, 4k iops unità fornita)
Andrew Hill

7

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';

Il trucco è nelle OFFSET 1 ROWS, che salterà il primo oggetto e restituirà il secondo oggetto. Se i tuoi indici sono basati su 0 e @X è la variabile che contiene l'indice degli articoli che vuoi recuperare, puoi sicuramente OFFSET @X ROWS
Gorgi Rankovski

Va bene, non l'ho mai usato prima ... Bello sapere ... Preferirei comunque l' xmlapproccio 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
Shnugo,

3
il problema qui è che STRING_SPLIT non garantisce l'ordine dei risultati restituiti. Quindi il tuo articolo 1 può essere o meno il mio articolo 1.
user1443098

@GorgiRankovski, Utilizzo delle STRING_SPLITrichieste per v2016 +. In questo caso è molto meglio usare OPENJSONo JSON_VALUE. Potresti voler controllare la mia risposta
Shnugo,

6

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

Non è possibile accedere facilmente all'ennesimo elemento usando questa funzione.
Björn Lindqvist,

6

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

Mi piace questa soluzione come un'opzione per restituire una singola sottostringa anziché ottenere una tabella analizzata che è quindi necessario selezionare. L'uso di un risultato di tabella ha i suoi usi, ma per quello di cui avevo bisogno funzionava perfettamente.
James H,

5

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'ho passato ed è perfettamente come quello che voglio! anche io posso anche personalizzarlo per ignorare i caratteri speciali che scelgo!
Vikas,

5

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


2


    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

2

È 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); 

1

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

SQL FIDDLE

vantaggi:

  • Separa tutti i 3 delimitatori di sottostringhe da ''.
  • Non si deve usare il ciclo while, poiché diminuisce le prestazioni.
  • Non è necessario eseguire il pivot in quanto tutta la stringa secondaria risultante verrà visualizzata in una riga

limitazioni:

  • Bisogna sapere il totale no. di spazi (sottostringa).

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 LOOPSpoiché peggiorerà le prestazioni


1

Pura soluzione basata su set usando TVFcon ricorsivo CTE. È possibile JOINe APPLYquesta 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

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.

0

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

Risultati :

|    A_L | N |
|--------|---|
| Hello  | 0 |
|  John  | 1 |
| Smith  | 2 |

0

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


0
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','')

0

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
  )

0

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ì.


0

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

funziona solo se sappiamo quanti token avrà la stringa - una limitazione alla rottura ...
Shnugo

0
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))

0

A partire da SQL Server 2016 abbiamo string_split

DECLARE @string varchar(100) = 'Richard, Mike, Mark'

SELECT value FROM string_split(@string, ',')

Questo va bene, ma non affronta la questione di ottenere l'ennesimo risultato.
Johnie Karr,

STRING_SPLITnon garantisce la restituzione dello stesso ordine. Ma OPENJSON(vedi la mia risposta (sezione aggiornamento) )
Shnugo
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.