it-swarm.com.de

T-SQL - Aufteilen (Zeichen getrennt) in Zeilen und Spalten

Was ist der beste Weg, um eine durch Zeichen getrennte Zeichenfolge in Zeilen und Spalten aufzuteilen?

Hier ist meine Eingabezeichenfolge:

!1;100;10;200;0;500;2;1000;30!2;100;3;500;1;2000;5

Und hier ist meine gewünschte Ausgabetabelle:

Id  Value  Count
1   100    10
1   200    0
1   500    2
1   1000   30
2   100    3
2   500    1
2   2000   5

Freundliche Grüße,

7
Tpsamw1

Es ist nicht notwendig, Jeff Modens Funktion zu ändern. Ich habe eine etwas ausgefeilte Methode entwickelt, um das Ergebnis nur mit der ursprünglichen Funktion zu erzielen.

Hier ist zunächst die ursprüngliche Moden-Funktion: (Quelle: http://www.sqlservercentral.com/articles/Tally+Table/72993/ )

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

Und die Abfrage zum Erhalten des erforderlichen Ergebnisses (ich habe einen CTE nur zur Klarheit erstellt)

DECLARE @string VARCHAR(8000)
SET @string = '!1;100;10;200;0;500;2;1000;30!2;100;3;500;1;2000;5'

;WITH CTE AS
(
SELECT 
SUBSTRING(A.Item,1,CHARINDEX(';',A.Item)-1) As Id, 
B.Item
FROM dbo.DelimitedSplit8K(@string, '!') AS A
CROSS APPLY dbo.DelimitedSplit8K(A.Item, ';') AS B
WHERE A.Item <> '' --to avoid blank first row because the first !
)

SELECT Id, [Value], [Count]
FROM
(
SELECT Id, Item As [Value], 
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Id) As RowNumber,
LEAD(Item,1,0) OVER(PARTITION BY Id ORDER BY Id) As [Count]
FROM CTE
) As T
WHERE RowNumber % 2 = 0

BEARBEITET: Ich erkannte, dass meine Lösung davon ausging, dass id Wert bestellt wurde oder dass die Reihenfolge in der ursprünglichen Zeichenfolge nicht erforderlich war, um im Ergebnis erhalten zu bleiben. Unterhalb der allgemeinen Lösung (beachten Sie, dass ich die erste Zahl in der Zeichenfolge geändert habe, um die Situation zu testen)

DECLARE @string VARCHAR(8000)
SET @string = '!3;100;10;200;0;500;2;1000;30!2;100;3;500;1;2000;5'
;WITH CTE AS
(
SELECT 
ROW_NUMBER() OVER(ORDER BY (SELECT 0)) As RN,
CAST(SUBSTRING(A.Item,1,CHARINDEX(';',A.Item)-1) AS Varchar(400)) As Id, 
B.Item
FROM dbo.DelimitedSplit8K(@string, '!') AS A
CROSS APPLY dbo.DelimitedSplit8K(A.Item, ';') AS B
WHERE A.Item <> '' --to avoid blank first row because the first !
)

SELECT Id, [Value], [Count]
FROM
(
SELECT Id, Item As [Value], 
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Id) As RowNumber,
LEAD(Item,1,0) OVER(PARTITION BY Id ORDER BY Id) As [Count]
,FIRST_VALUE(RN) OVER(PARTITION BY Id ORDER BY Id) AS FV
FROM CTE
) As T
WHERE RowNumber % 2 = 0
ORDER BY FV

http://sqlfiddle.com/#!6/31ccd/

2
JGA

Jeff Moden hat auf www.SQLServerCentral.com eine Splitter-Funktion namens DelimitedSplit8K erstellt. Es werden zwei Parameter benötigt: @pString (die zu teilende Zeichenfolge) und @pDelimiter (in Ihrem Fall das Trennzeichen;).

In Ihrem Fall führen Sie jedoch keine direkte Aufteilung durch, sodass diese ein wenig geändert werden muss. Tatsächlich müssen wir zwei verschiedene Arten von Teilungen durchführen. Der erste ist ein normaler, auf den man sich trennen kann! und die zweite eine modifizierte, um die verschiedenen Zeilen zu erhalten, wobei der erste Wert als erste Spalte verwendet wird und der Rest paarweise aufgeteilt wird, ein Paar pro Zeile.

Testcode zuerst

DECLARE @splitstring varchar(8000)
SET @splitstring = '!1;100;10;200;0;500;2;1000;30!2;100;3;500;1;2000;5'
-- First use the original function to split out the groups.
-- The STUFF function removes the first ! since this is a split function
-- and if it's left we get a blank line.
SELECT FinalDelim.* 
FROM DelimitedSplit8K(STUFF(@splitstring,1,1,''),'!') AS FirstDelim
-- Here we CROSS APPLY the modified version over each of the original splits
CROSS APPLY ModifiedDelimitedSplit8K(FirstDelim.Item,';') AS FinalDelim
GO

Und hier ist der ModifiedDelimitedSplit8K-Code.

-- Based Jeff Modem's original script
CREATE FUNCTION [dbo].[ModifiedDelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                ),
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
-- Putting this in it's own CTE since we need ItemNumber 1 for all of the rows
cteSplitString(ItemNumber, Item) AS (
     SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
            Item       = SUBSTRING(@pString, l.N1, l.L1)
    FROM cteLen l)
SELECT cteItem1.Item AS Id,
    cteItem2.Value,
    cteItem2.Count
FROM (SELECT Item FROM cteSplitString WHERE ItemNumber = 1) AS cteItem1
CROSS JOIN (SELECT MAX(CASE WHEN ItemNumber%2 = 0 THEN Item ELSE NULL END) AS Value,
                    MAX(CASE WHEN ItemNumber%2 = 1 THEN Item ELSE NULL END) AS Count
            FROM cteSplitString 
            WHERE ItemNumber > 1
            GROUP BY ItemNumber/2) AS cteItem2
0
Kenneth Fisher

Dies ist wahrscheinlich nicht die beste Lösung. Da Ihr Vorschlag jedoch etwas speziell war, habe ich beschlossen, eine Ad-hoc-Lösung zu erstellen. Es hat Spaß gemacht. Kann hier getestet werden: http://sqlfiddle.com/#!3/9eecb7db59d16c80417c72d1/62

    declare @string varchar(max) 
    set @string = '!1;100;10;200;0;500;2;1000;30!2;100;3;500;1;2000;5'

    declare @t1  table (row varchar(max))

    declare @end int 
    set @end = 1

    set @string = substring ( @string ,2, len(@string))
    while charindex ( '!' ,@string,1) > 0
    begin
        set @end = charindex ( '!' ,@string,1)
        insert into @t1 (row) values (substring ( @string ,1, @end - 1))
        set @string = substring ( @string ,@end+1, len(@string))
    end
    set @end = len(@string)

    insert into @t1 (row) values (substring ( @string ,1, @end))

    declare cur cursor for select * from @t1
    open cur

    declare @row varchar(max)
    fetch next from cur into @row
    declare @result table
    (id varchar(max), [value] varchar(max), [count] varchar(max))
    declare @id varchar(max), @value varchar(max), @count varchar(max)
    while @@fetch_status = 0
    begin
        set @end = charindex( ';' ,@row)
        set @id = substring(@row,1,@end-1)
        set @row = substring(@row,@end +1,len(@row))
        while len(@row) > 0 
        begin
            set @end = charindex( ';' ,@row)
            set @value = substring(@row,1,@end-1)
            set @row = substring(@row,@end +1,len(@row))
            set @end = charindex( ';' ,@row)
            if @end > 0 
            begin
                set @count = substring(@row,1,@end-1)
                set @row = substring(@row,@end +1,len(@row))
                set @end = charindex( ';' ,@row)
            end
            else 
            begin
                set @count = substring(@row,1,len(@row))
                set @row = ''
            end
            insert into @result (id, [value], [count]) values (@id, @value, @count)
        end
        fetch next from cur into @row   
    end
close cur
deallocate cur   
select * from @result
0
JGA