T-SQL-Split-String


139

Ich habe eine SQL Server 2008 R2-Spalte mit einer Zeichenfolge, die ich durch ein Komma teilen muss. Ich habe viele Antworten auf StackOverflow gesehen, aber keine davon funktioniert in R2. Ich habe sichergestellt, dass ich ausgewählte Berechtigungen für Beispiele für geteilte Funktionen habe. Jede Hilfe sehr geschätzt.


7
Dies ist eine der Millionen Antworten, die ich mag stackoverflow.com/a/1846561/227755
nurettin

2
Was meinst du mit "keiner von ihnen funktioniert"? Kannst du genauer sein?
Aaron Bertrand

Andy hat mich in die richtige Richtung gelenkt, da ich die Funktion falsch ausgeführt habe. Aus diesem Grund hat keine der anderen Stapelantworten funktioniert. Mein Fehler.
Lee Grindon

2
Mögliches Duplikat der geteilten Zeichenfolge in SQL
Luv

Das mdq.RegexSplitAdd-On "Stammdatendienste" enthält eine Funktion, die möglicherweise hilfreich ist. Auf jeden Fall eine Untersuchung wert .
Jpaugh

Antworten:


233

Ich habe diese SQL verwendet, die für Sie möglicherweise funktioniert: -

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

und um es zu benutzen: -

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

1
Schön, genau das habe ich gesucht, vielen Dank
Lee Grindon

2
Vielen Dank Andy. Ich habe Ihr Skript geringfügig erweitert, damit die Funktion ein Element an einem bestimmten Index in der geteilten Zeichenfolge zurückgeben kann. Dies ist nur in Situationen nützlich, in denen Sie die Struktur der Spalte analysieren. gist.github.com/klimaye/8147193
CF_Maintainer

1
Ich stellte einige Verbesserungen (mit Testfällen Sicherung) auf meine Github Seite hier . Ich werde es als Antwort in diesem Stack Overflow- Thread veröffentlichen, wenn ich genug Wiederholungen habe, um den Beitrag "Schutz" zu überschreiten
mpag

8
Obwohl dies eine großartige Antwort ist, ist sie veraltet ... Prozedurale Ansätze (insbesondere Schleifen) sollten
vermieden werden

2
Ich stimme @Shnugo voll und ganz zu. Die Schleifensplitter arbeiten aber schrecklich langsam. So etwas wie dieses ist sqlservercentral.com/articles/Tally+Table/72993 weitaus besser. Einige andere ausgezeichnete Set-basierte Optionen finden Sie hier. sqlperformance.com/2012/07/t-sql-queries/split-strings
Sean Lange

61

Hat jemand anstelle von rekursiven CTEs und while-Schleifen einen satzbasierten Ansatz in Betracht gezogen? Beachten Sie, dass diese Funktion für die Frage geschrieben wurde, die auf SQL Server 2008 und Komma als Trennzeichen basierte . In SQL Server 2016 und höher (und in Kompatibilitätsstufe 130 und höher) STRING_SPLIT()ist dies eine bessere Option .

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

Wenn Sie vermeiden möchten, dass die Länge der Zeichenfolge <= die Anzahl der Zeilen in sys.all_columns(9.980 in modelin SQL Server 2017; viel höher in Ihren eigenen Benutzerdatenbanken) ist, können Sie andere Ansätze zum Ableiten der Zahlen verwenden, z Erstellen Sie Ihre eigene Zahlentabelle . Sie können auch einen rekursiven CTE verwenden, wenn Sie keine Systemtabellen verwenden oder keine eigenen erstellen können:

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

Sie müssen jedoch an die äußere Abfrage anhängen OPTION (MAXRECURSION 0)(oder MAXRECURSION <longest possible string length if < 32768>), um Fehler bei der Rekursion für Zeichenfolgen> 100 Zeichen zu vermeiden. Wenn das auch keine gute Alternative ist, dann sehen Sie diese Antwort wie in den Kommentaren angegeben.

(Auch das Trennzeichen muss sein NCHAR(<=1228). Ich recherchiere immer noch warum.)

Weitere Informationen zu Teilungsfunktionen, warum (und beweisen Sie dies), während Schleifen und rekursive CTEs nicht skaliert werden, und bessere Alternativen, wenn Zeichenfolgen aus der Anwendungsschicht aufgeteilt werden:


1
Es gibt einen kleinen Fehler in dieser Prozedur für den Fall, dass am Ende der Zeichenfolge ein Nullwert steht - wie in '1,2 ,, 4,' -, da der Endwert nicht analysiert wird. Um diesen Fehler zu beheben, sollte der Ausdruck "WHERE-Nummer <= LEN (@List)" durch "WHERE-Nummer <= LEN (@List) + 1" ersetzt werden.
SylvainL

@ SylvainL Ich denke, das hängt davon ab, welches Verhalten Sie wollen. Nach meiner Erfahrung möchten die meisten Leute nachgestellte Kommas ignorieren, da sie kein echtes Element darstellen (wie viele Kopien einer leeren Zeichenfolge benötigen Sie)? Wie auch immer, die wahre Weg, dies zu tun - wenn Sie dem zweiten Link folgen - besteht darin, in hässlichem T-SQL ohnehin große hässliche Strings aufzuteilen.
Aaron Bertrand

1
Wie Sie gesagt haben, möchten die meisten Leute nachgestellte Kommas ignorieren, aber leider nicht alle. Ich nehme an, dass eine vollständigere Lösung darin besteht, einen Parameter hinzuzufügen, um anzugeben, was in diesem Fall zu tun ist, aber mein Kommentar ist nur eine kleine Anmerkung, um sicherzustellen, dass niemand diese Möglichkeit vergisst, da sie in vielen Fällen durchaus real sein kann.
SylvainL

Ich habe ein komisches Verhalten mit dieser Funktion. Wenn ich direkt einen String als Parameter verwende, funktioniert das. Wenn ich einen Varchar habe, ist dies nicht der Fall. Sie können leicht reproduzieren: deklarieren Sie invarchar als varchar set invarchar = 'ta; aa; qq' SELECT-Wert von [dbo]. [SplitString] (invarchar, ';') SELECT-Wert von [dbo]. [SplitString] ('ta; aa; qq ','; ')
Patrick Desjardins

Ich mag diesen Ansatz, aber wenn die Anzahl der von zurückgegebenen Objekte sys.all_objectsgeringer ist als die Anzahl der Zeichen in der Eingabezeichenfolge, wird die Zeichenfolge abgeschnitten und die Werte gehen verloren. Da dies sys.all_objectsnur als Hack zum Generieren von Zeilen verwendet wird, gibt es bessere Möglichkeiten, dies zu tun, z . B. diese Antwort .
Knöchel

56

Endlich ist das Warten in SQL Server 2016 vorbei. Sie haben die Split-String-Funktion eingeführt:STRING_SPLIT

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

Alle anderen Methoden zum Teilen von Zeichenfolgen wie XML, Tally-Tabelle, while-Schleife usw. wurden dadurch weggeblasen STRING_SPLIT Funktion .

Hier ist ein ausgezeichneter Artikel mit Leistungsvergleich: Leistungsüberraschungen und Annahmen: STRING_SPLIT


4
Offensichtlich beantwortet die Frage, wie Zeichenfolgen für diejenigen mit aktualisierten Servern aufgeteilt werden sollen, aber diejenigen von uns, die noch an 2008 / 2008R2 hängen, müssen mit einer der anderen Antworten hier gehen.
mpag

2
Sie müssen sich die Kompatibilitätsstufe in Ihrer Datenbank ansehen. Wenn es niedriger als 130 ist, können Sie die Funktion STRING_SPLIT nicht verwenden.
Luis Teijon

Wenn die Kompatibilität nicht 130 ist und Sie 2016 (oder Azure SQL) ausführen, können Sie die Kompatibilität auf 130 einstellen, indem Sie Folgendes verwenden: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Michieal

23

Der einfachste Weg, dies zu tun, ist die Verwendung von XML Formats.

1. Konvertieren von Zeichenfolgen in Zeilen ohne Tabelle

ABFRAGE

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)

ERGEBNIS

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

2. Konvertieren in Zeilen aus einer Tabelle, die eine ID für jede CSV-Zeile haben

QUELLENTABELLE

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

ABFRAGE

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

ERGEBNIS

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

Dieser Ansatz wird unterbrochen, wenn er @Stringverbotene Zeichen enthält ... Ich habe gerade eine Antwort gepostet , um dieses Problem zu beheben.
Shnugo

9

Ich brauchte einen schnellen Weg , um von der zu befreien +4von einem Postleitzahl .

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

Kein Prozess ... kein UDF ... nur ein kleiner kleiner Inline-Befehl, der das tut, was er muss. Nicht schick, nicht elegant.

Ändern Sie das Trennzeichen nach Bedarf usw., und es funktioniert für alles.


4
Darum geht es in der Frage nicht. Das OP hat einen Wert wie '234,542,23' und sie möchten ihn in drei Zeilen aufteilen ... 1. Zeile: 234, 2. Zeile: 542, 3. Zeile: 23. In SQL ist dies eine schwierige Aufgabe.
Codeulike

7

wenn Sie ersetzen

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

mit

WHILE LEN(@stringToSplit) > 0

Sie können diese letzte Einfügung nach der while-Schleife entfernen!

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

Dies würde dazu führen, dass das letzte Zeichen des letzten Elements abgeschnitten wird. dh "AL, AL" würde zu "AL" | werden "A", dh "ABC, ABC, ABC", würde zu "ABC" | "ABC" | "AB"
Microsoft Developer

Das Anhängen +1an SELECT @pos = LEN(@stringToSplit)scheint dieses Problem zu beheben. Das SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)wird jedoch zurückgegeben, Invalid length parameter passed to the LEFT or SUBSTRING functionsofern Sie nicht auch +1den dritten Parameter von SUBSTRING hinzufügen . oder Sie könnten diese Aufgabe durchSET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
mpag

1
Ich stellte einige Verbesserungen (mit Testfällen Sicherung) auf meine Github Seite hier . Ich werde es als Antwort in diesem Stack Overflow- Thread veröffentlichen, wenn ich genug Wiederholungen habe, um den Beitrag "Schutz" zu überschreiten
mpag

Ich habe auch das Problem bemerkt, auf das Terry oben hingewiesen hat. Aber die von @AviG gegebene Logik ist so cool, dass sie bei einer langen Liste von Token nicht in der Mitte fehlschlägt. Versuchen Sie diesen Testaufruf, um zu überprüfen (dieser Aufruf sollte 969 Token zurückgeben), wählen Sie * aus dbo.splitstring ('token1, token2 ,,,,,,, token969'). Dann habe ich den von mpag angegebenen Code versucht, um die Ergebnisse auf dasselbe zu überprüfen Rufen Sie oben an und stellen Sie fest, dass nur 365 Token zurückgegeben werden können. Schließlich habe ich oben den Code von AviG korrigiert und die fehlerfreie Funktion als neue Antwort unten veröffentlicht, da der Kommentar hier nur begrenzten Text zulässt. Überprüfen Sie die Antwort unter meinem Namen, um es zu versuchen.
Gemunu R Wickremasinghe

3

Alle Funktionen zum Teilen von Zeichenfolgen, die eine Art Schleifen (Iterationen) verwenden, weisen eine schlechte Leistung auf. Sie sollten durch eine satzbasierte Lösung ersetzt werden.

Dieser Code wird ausgezeichnet ausgeführt.

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

Dieser Ansatz wird unterbrochen, wenn er @Listverbotene Zeichen enthält ... Ich habe gerade eine Antwort gepostet , um dieses Problem zu beheben.
Shnugo

Ich stimme Ihrer Antwort zu, weil Ihre mit Leerzeichen als Trennzeichen arbeitet und die mit der höchsten Stimme nicht
KMC

3

Der häufig verwendete Ansatz mit XML-Elementen bricht bei verbotenen Zeichen ab. Dies ist ein Ansatz, um diese Methode mit jeder Art von Zeichen zu verwenden, auch mit dem Semikolon als Trennzeichen.

Der Trick besteht darin, zuerst SELECT SomeString AS [*] FOR XML PATH('')alle verbotenen Zeichen richtig zu entkommen. Das ist der Grund, warum ich das Trennzeichen durch einen magischen Wert ersetze , um Probleme mit ;dem Trennzeichen zu vermeiden .

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)

Das Ergebnis

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

2

Ich musste kürzlich so etwas schreiben. Hier ist die Lösung, die ich gefunden habe. Es ist für jede Trennzeichenfolge verallgemeinert und ich denke, es würde etwas besser funktionieren:

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

Eine Lösung mit einem CTE, falls jemand das brauchen sollte (abgesehen von mir, der es offensichtlich getan hat, habe ich es deshalb geschrieben).

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

Dies ist enger zugeschnitten. Wenn ich dies tue, habe ich normalerweise eine durch Kommas getrennte Liste eindeutiger IDs (INT oder BIGINT), die ich als Tabelle umwandeln möchte, um sie als innere Verknüpfung zu einer anderen Tabelle mit dem Primärschlüssel INT oder BIGINT zu verwenden. Ich möchte, dass eine Inline-Tabellenwertfunktion zurückgegeben wird, damit ich den effizientesten Join habe.

Beispielnutzung wäre:

 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

Ich habe die Idee von http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html gestohlen und sie so geändert, dass sie als Inline-Tabelle bewertet und als INT umgewandelt wird.

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

Es gibt hier eine korrekte Version, aber ich dachte, es wäre schön, ein wenig Fehlertoleranz hinzuzufügen, falls sie ein nachfolgendes Komma haben, und es so zu machen, dass Sie es nicht als Funktion, sondern als Teil eines größeren Codeteils verwenden können . Nur für den Fall, dass Sie es nur einmal verwenden und keine Funktion benötigen. Dies gilt auch für Ganzzahlen (wofür ich sie benötigt habe), sodass Sie möglicherweise Ihre Datentypen ändern müssen.

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

Wenn Sie SET @StringToSeperate = @StringToSeperate+','unmittelbar vor der WHILESchleife wären, könnten Sie möglicherweise den Block "letzten Wert hinzufügen" entfernen. Siehe auch mein Sol'n auf Github
mpag

Auf welcher Antwort basiert das? Hier gibt es viele Antworten und es ist ein bisschen verwirrend. Vielen Dank.
Jpaugh

1

Ich habe die Funktion von + Andy Robinson ein wenig modifiziert. Jetzt können Sie nur das erforderliche Teil aus der Rückgabetabelle auswählen:

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

Wenn Sie eine schnelle Ad-hoc-Lösung für häufige Fälle mit minimalem Code benötigen, ist dieser rekursive CTE-Zweiliner genau das Richtige für Sie:

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

Verwenden Sie dies entweder als eigenständige Anweisung oder fügen Sie einfach die oben genannten CTEs zu einer Ihrer Abfragen hinzu, und Sie können die resultierende Tabelle bmit anderen verknüpfen, um sie in weiteren Ausdrücken zu verwenden.

bearbeiten (von Shnugo)

Wenn Sie einen Zähler hinzufügen, erhalten Sie zusammen mit der Liste einen Positionsindex:

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;

Das Ergebnis:

n   s
1   1
2   2333
3   344
4   4

Ich mag diesen Ansatz. Ich hoffe, es macht Ihnen nichts aus, dass ich Ihre Antwort direkt verbessert habe. Fühlen Sie sich einfach frei, dies auf eine bequeme Weise zu bearbeiten ...
Shnugo

1

Ich gehe die XML-Route, indem ich die Werte in Elemente einbinde (M, aber alles funktioniert):

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

Hier ist eine Version, die mithilfe von Patindex, einer einfachen Anpassung des obigen Beitrags, nach einem Muster aufgeteilt werden kann. Ich hatte einen Fall, in dem ich eine Zeichenfolge teilen musste, die mehrere Trennzeichen enthielt.


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','%[/,]%');

Ergebnis sieht so aus

stringa stringb x y z


0

Persönlich benutze ich diese Funktion:

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

Ich habe ein Doppel Splitter (Dauert zwei geteilte Zeichen) entwickelt wie gewünscht hier . Könnte in diesem Thread von Wert sein, da er für Abfragen im Zusammenhang mit der Aufteilung von Zeichenfolgen am häufigsten verwendet wird.

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

Verwendung:

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

Mögliche Verwendung (Ermitteln Sie den zweiten Wert für jeden Split):

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

Hier ist ein Beispiel, das Sie als Funktion verwenden können, oder Sie können dieselbe Logik in die Prozedur einfügen. --SELECT * from [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;

Dieser Ansatz wird unterbrochen, wenn er @vCSVverbotene Zeichen enthält ... Ich habe gerade eine Antwort gepostet , um dieses Problem zu beheben.
Shnugo

0

Eine rekursive cte-basierte Lösung

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

Dies basiert auf der Antwort von Andy Robertson. Ich brauchte ein anderes Trennzeichen als Komma.

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

Und um es zu benutzen:

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

(Getestet auf SQL Server 2008 R2)

BEARBEITEN: korrekter Testcode


0

/ *

Antwort auf geteilte T-SQL-Zeichenfolge
Basierend auf den Antworten von Andy Robinson und AviG
Erweiterte Funktionalität ref: LEN-Funktion ohne nachgestellte Leerzeichen in SQL Server
Diese 'Datei' sollte sowohl als Markdown-Datei als auch als SQL-Datei gültig sein


*/

    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

/*

Testfälle: Siehe URL, auf die oben als "erweiterte Funktionalität" verwiesen wird

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

* /


Rollback zu Ehren "Diese 'Datei' sollte sowohl als Markdown-Datei als auch als SQL-Datei gültig sein"
mpag

-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

Sie können diese Funktion verwenden:

        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

Bei allem Respekt vor @AviG ist dies die fehlerfreie Version der Funktion, die er entwickelt hat, um alle Token vollständig zurückzugeben.

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

Der einfachste Weg:

  1. Installieren Sie SQL Server 2016
  2. Verwenden Sie STRING_SPLIT https://msdn.microsoft.com/en-us/library/mt684588.aspx

Es funktioniert sogar in der Express Edition :).


Vergessen Sie nicht, "Kompatibilitätsstufe" auf SQL Server 2016 (130) einzustellen. Klicken Sie in Management Studio mit der rechten Maustaste auf Datenbank, Eigenschaften / Optionen / Kompatibilitätsstufe.
Tomino

1
Der ursprüngliche Beitrag sagte für SQL 2008 R2. Die Installation von SQL 2016 ist möglicherweise keine Option
Shawn Gavett
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.