it-swarm.com.de

T-SQL: Im Gegensatz zur Verkettung von Zeichenfolgen - Aufteilen von Zeichenfolgen in mehrere Datensätze

Mögliches Duplikat:
Geteilte Zeichenfolge in SQL

Ich habe ein paar Fragen im Zusammenhang mit der Verkettung von Zeichenfolgen in SQL gesehen. Ich frage mich, wie Sie das gegenteilige Problem angehen würden: die Aufteilung einer durch Kommas getrennten Zeichenfolge in Datenzeilen:

Sagen wir, ich habe Tische:

userTypedTags(userID,commaSeparatedTags) 'one entry per user
tags(tagID,name)

Und wollen Daten in die Tabelle einfügen

userTag(userID,tagID) 'multiple entries per user

Inspiriert von Welche Tags sind nicht in der Datenbank? frage

[~ # ~] edit [~ # ~]

Vielen Dank für die Antworten, eigentlich verdient mehr als man akzeptiert werden, aber ich kann nur eine auswählen, und die Lösung von Cade Roux mit Rekursionen scheint mir ziemlich sauber. Es funktioniert mit SQL Server 2005 und höher.

Für frühere Versionen von SQL Server kann die Lösung bereitgestellt von miies verwendet werden. Für die Arbeit mit Textdatentyp wcm Antwort wird hilfreich sein. Danke noch einmal.

135
kristof

Es gibt eine Vielzahl von Lösungen für dieses Problem hier dokumentiert , einschließlich dieses kleinen Juwel:

CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )
147
Cade Roux

Sie können diesen Effekt auch mit XML erzielen, wie hier zu sehen , wodurch die Begrenzung der Antworten aufgehoben wird, die alle in gewisser Weise eine Rekursion beinhalten. Die besondere Verwendung, die ich hier vorgenommen habe, ermöglicht ein Begrenzungszeichen mit bis zu 32 Zeichen, das jedoch erhöht werden kann, wie groß es sein muss.

create FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(REPLACE(REPLACE(@s,'& ','&amp; '),'<','&lt;'), @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )

Dann können Sie es aufrufen mit:

SELECT * FROM dbo.Split(' ', 'I hate bunnies')

Welches gibt zurück:

-----------
|I        |
|---------|
|hate     |
|---------|
|bunnies  |
-----------


CREATE FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )
84
Nathan Wheeler

Ich benutze diese Funktion (SQL Server 2005 und höher).

create function [dbo].[Split]
(
    @string nvarchar(4000),
    @delimiter nvarchar(10)
)
returns @table table
(
    [Value] nvarchar(4000)
)
begin
    declare @nextString nvarchar(4000)
    declare @pos int, @nextPos int

    set @nextString = ''
    set @string = @string + @delimiter

    set @pos = charindex(@delimiter, @string)
    set @nextPos = 1
    while (@pos <> 0)
    begin
        set @nextString = substring(@string, 1, @pos - 1)

        insert into @table
        (
            [Value]
        )
        values
        (
            @nextString
        )

        set @string = substring(@string, @pos + len(@delimiter), len(@string))
        set @nextPos = @pos
        set @pos = charindex(@delimiter, @string)
    end
    return
end
18
user39603

Für den speziellen Fall des Aufteilens von Zeichenfolgen in Wörter bin ich auf eine andere Lösung für SQL Server 2008 gestoßen.

with testTable AS
(
SELECT 1 AS Id, N'how now brown cow' AS txt UNION ALL
SELECT 2, N'she sells sea shells upon the sea shore' UNION ALL
SELECT 3, N'red lorry yellow lorry' UNION ALL
SELECT 4, N'the quick brown fox jumped over the lazy dog'
)

SELECT display_term, COUNT(*) As Cnt
 FROM testTable
CROSS APPLY sys.dm_fts_parser('"' + txt + '"', 1033, 0,0)
GROUP BY display_term
HAVING COUNT(*) > 1
ORDER BY Cnt DESC

Kehrt zurück

display_term                   Cnt
------------------------------ -----------
the                            3
brown                          2
lorry                          2
sea                            2
11
Martin Smith

Leichte Änderung von die Lösung oben, damit es mit Begrenzern variabler Länge funktioniert.

create FUNCTION dbo.fn_Split2 (@sep nvarchar(10), @s nvarchar(4000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + (datalength(@sep)/2), CHARINDEX(@sep, @s, stop + (datalength(@sep)/2))
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 4000 END) AS s
    FROM Pieces
  )

NB: Ich habe datalength () verwendet, da len () falsch meldet, wenn nachgestellte Leerzeichen vorhanden sind.

7
Rory

Hier ist eine Split -Funktion, die mit SQL Server-Versionen vor 2005 kompatibel ist.

CREATE FUNCTION dbo.Split(@data nvarchar(4000), @delimiter nvarchar(100))  
RETURNS @result table (Id int identity(1,1), Data nvarchar(4000)) 
AS  
BEGIN 
    DECLARE @pos   INT
    DECLARE @start INT
    DECLARE @len   INT
    DECLARE @end   INT

    SET @len   = LEN('.' + @delimiter + '.') - 2
    SET @end   = LEN(@data) + 1
    SET @start = 1
    SET @pos   = 0

    WHILE (@pos < @end)
    BEGIN
        SET @pos = CHARINDEX(@delimiter, @data, @start)
        IF (@pos = 0) SET @pos = @end

        INSERT @result (data) SELECT SUBSTRING(@data, @start, @pos - @start)
        SET @start = @pos + @len
    END

    RETURN
END
7
Tomalak

Mit CLR ist dies eine viel einfachere Alternative, die in allen Fällen funktioniert und dennoch 40% schneller als die akzeptierte Antwort ist:

using System;
using System.Collections;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

public class UDF
{
    [SqlFunction(FillRowMethodName="FillRow")]
    public static IEnumerable RegexSplit(SqlString s, SqlString delimiter)
    {
        return Regex.Split(s.Value, delimiter.Value);
    }

    public static void FillRow(object row, out SqlString str)
    {
        str = new SqlString((string) row);
    }
}

Natürlich ist es immer noch achtmal langsamer als PostgreSQLs regexp_split_to_table.

7
sayap
SELECT substring(commaSeparatedTags,0,charindex(',',commaSeparatedTags))

gibt dir den ersten tag. Sie können auf ähnliche Weise vorgehen, um die zweite zu erhalten, indem Sie Teilzeichenfolge und Zeichenindex jeweils eine Ebene tiefer kombinieren. Dies ist eine sofortige Lösung, funktioniert jedoch nur mit sehr wenigen Tags, da die Abfrage sehr schnell größer und unleserlich wird. Gehen Sie dann zu Funktionen über, wie in anderen, anspruchsvolleren Antworten auf diesen Beitrag beschrieben.

5
Yann Semet

Ich habe die Antwort von "Nathan Wheeler" hochgestuft, da ich fand, dass die Antwort von "Cade Roux" oberhalb einer bestimmten Stringgröße nicht funktioniert hat.

Ein paar Punkte

-Ich habe festgestellt, dass das Hinzufügen des Schlüsselworts DISTINCT die Leistung für mich verbessert hat.

-Nathans Antwort funktioniert nur, wenn Ihre Bezeichner aus 5 oder weniger Zeichen bestehen. Natürlich können Sie dies anpassen. Wenn die Elemente, die Sie teilen, INT Bezeichner als Ich bin du kannst uns genauso wie ich unten:

CREATE FUNCTION [dbo].Split
(
    @sep VARCHAR(32), 
    @s VARCHAR(MAX)
)
RETURNS 
    @result TABLE (
        Id INT NULL
    )   
AS
BEGIN
    DECLARE @xml XML
    SET @XML = N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>'

    INSERT INTO @result(Id)
    SELECT DISTINCT r.value('.','int') as Item
    FROM @xml.nodes('//root//r') AS RECORDS(r)

    RETURN
END
2
Darren

Ich habe das vor einiger Zeit geschrieben. Es wird davon ausgegangen, dass das Trennzeichen ein Komma ist und die einzelnen Werte nicht länger als 127 Zeichen sind. Es könnte ziemlich leicht modifiziert werden.

Es hat den Vorteil, dass es nicht auf 4.000 Zeichen beschränkt ist.

Viel Glück!

ALTER Function [dbo].[SplitStr] ( 
        @txt text 
) 
Returns @tmp Table 
        ( 
                value varchar(127)
        ) 
as 
BEGIN 
        declare @str varchar(8000) 
                , @Beg int 
                , @last int 
                , @size int 

        set @size=datalength(@txt) 
        set @Beg=1 


        set @str=substring(@txt,@Beg,8000) 
        IF len(@str)<8000 set @[email protected] 
        ELSE BEGIN 
                set @last=charindex(',', reverse(@str)) 
                set @str=substring(@txt,@Beg,[email protected]) 
                set @[email protected][email protected]+1 
        END 

        declare @workingString varchar(25) 
                , @stringindex int 



        while @Beg<[email protected] Begin 
                WHILE LEN(@str) > 0 BEGIN 
                        SELECT @StringIndex = CHARINDEX(',', @str) 

                        SELECT 
                                @workingString = CASE 
                                        WHEN @StringIndex > 0 THEN SUBSTRING(@str, 1, @StringIndex-1) 
                                        ELSE @str 
                                END 

                        INSERT INTO 
                                @tmp(value)
                        VALUES 
                                (cast(rtrim(ltrim(@workingString)) as varchar(127)))
                        SELECT @str = CASE 
                                WHEN CHARINDEX(',', @str) > 0 THEN SUBSTRING(@str, @StringIndex+1, LEN(@str)) 
                                ELSE '' 
                        END 
                END 
                set @str=substring(@txt,@Beg,8000) 

                if @[email protected] set @[email protected]+1 
                else IF len(@str)<8000 set @[email protected] 
                ELSE BEGIN 
                        set @last=charindex(',', reverse(@str)) 
                        set @str=substring(@txt,@Beg,[email protected]) 
                        set @[email protected][email protected]+1 

                END 
        END     

        return
END 
2
wcm

Normalerweise mache ich das mit folgendem Code:

create function [dbo].[Split](@string varchar(max), @separator varchar(10))
returns @splited table ( stringPart varchar(max) )
with execute as caller
as
begin
    declare @stringPart varchar(max);
    set @stringPart = '';

    while charindex(@separator, @string) > 0
    begin
        set @stringPart = substring(@string, 0, charindex(@separator, @string));
        insert into @splited (stringPart) values (@stringPart);
        set @string = substring(@string, charindex(@separator, @string) + len(@separator), len(@string) + 1);
    end

    return;
end
go

Sie können es mit dieser Abfrage testen:

declare @example varchar(max);
set @example = 'one;string;to;rule;them;all;;';

select * from [dbo].[Split](@example, ';');
0
Marek