it-swarm.com.de

Verkettung mehrerer Spalten

Wie verkette ich mehrere Spalten zu einer einzigen Zeile? Zum Beispiel:

id   name    car
1    sam     dodge
1    ram     maserati
1    john    benz
1    NULL    mazda
2    kirk    lexus
2    Jim     rolls
1            GMC

Die erwartete Ergebnismenge lautet:

ID  name               car
1   sam,ram,john       dodge,maserati,benz,mazda,GMC
2   kirk,jim           lexus,rolls

Verwenden einer Lösung Ich habe bei Stack Overflow gefunden:

SELECT * FROM (
SELECT t.id,stuff([m].query('/name').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined1],
stuff([m].query('/car').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined2]
FROM dbo.test t
OUTER apply(SELECT (

SELECT id, ','+name AS name
,','+car AS car
FROM test WHERE test.id=t.id
FOR XML PATH('') ,type)
             AS  M) A)S
GROUP BY id,somefield_combined1,somefield_combined2 

Gibt es bessere Lösungen? Die innere Auswahl stammt aus einem teuren Multi-Table-Join (nicht dem oben gezeigten Single-Table-Test). Die Abfrage befindet sich in einer Inline-TVF, daher kann ich keine temporäre Tabelle verwenden.

Wenn eine leere Spalte vorhanden ist, ergeben die Ergebnisse zusätzliche Kommas wie

ID  name                car
1   sam,ram,john,,      dodge,maserati,benz,mazda,GMC
2   kirk,jim           lexus,rolls

Gibt es eine Möglichkeit, dies zu verhindern?

6
Biju jose

Ich habe ein paar Tests mit etwas mehr als 6 mil Reihen durchgeführt. Mit einem Index in der ID-Spalte.

Folgendes habe ich mir ausgedacht.

Ihre erste Anfrage:

SELECT * FROM (
    SELECT t.id,
            stuff([M].query('/name').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined1],
            stuff([M].query('/car').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined2]
    FROM dbo.test t
    OUTER APPLY(SELECT (
                    SELECT id, ','+name AS name
                    ,','+car AS car
                    FROM test WHERE test.id=t.id
                    FOR XML PATH('') ,type)
                 AS  M) 
            M ) S
GROUP BY id, SomeField_Combined1, SomeField_Combined2 

Dieser lief ~ 23 Minuten.

Ich habe diese Version ausgeführt, die ich zuerst gelernt habe. In gewisser Weise scheint es länger zu dauern, aber das tut es nicht.

SELECT test.id,
    STUFF((SELECT ', ' + ThisTable.name
            FROM   test ThisTable
            WHERE  test.id = ThisTable.id
            AND    ThisTable.name <> ''
            FOR XML PATH ('')),1,2,'') AS ConcatenatedSomeField,
    STUFF((SELECT ', ' + car
            FROM   test ThisTable
            WHERE  test.id = ThisTable.id
            AND    ThisTable.car <> ''
            FOR XML PATH ('')),1,2,'') AS ConcatenatedSomeField2
FROM test 
GROUP BY id

Diese Version lief in etwas mehr als 2 Minuten.

5
Kenneth Fisher

Ein CLR-Aggregat ist mit ziemlicher Sicherheit der schnellste Weg, dies zu tun. Aber vielleicht möchten Sie aus irgendeinem Grund keine verwenden ...

Sie sagen, dass die Quelle dafür eine teure Abfrage ist.

Ich würde dies zuerst in einer #temp - Tabelle materialisieren, um sicherzustellen, dass es nur einmal ausgewertet wird.

CREATE TABLE #test
(
ID INT,
name NVARCHAR(128),
car  NVARCHAR(128)
);

CREATE CLUSTERED INDEX ix ON #test(ID);

Der Ausführungsplan, den ich für die Abfrage in der Frage erhalte, führt zuerst die Verkettung für jede Zeile in der äußeren Abfrage durch und entfernt dann die Duplikate mit id, SomeField_Combined1, SomeField_Combined2.

Das ist unglaublich verschwenderisch. Das folgende Umschreiben vermeidet dies.

SELECT t.id,
       stuff([M].query('/name').value('/', 'varchar(max)'), 1, 1, '') AS [SomeField_Combined1],
       stuff([M].query('/car').value('/', 'varchar(max)'), 1, 1, '')  AS [SomeField_Combined2]
FROM   (SELECT DISTINCT id
        FROM   #test) t
       OUTER APPLY(SELECT (SELECT id,
                                  ',' + name AS name,
                                  ',' + car  AS car
                           FROM   #test
                           WHERE  #test.id = t.id
                           FOR XML PATH(''), type) AS M) M 

Jedoch für die folgenden Testdaten (1000 IDs mit 2156 Zeilen pro ID für mich)

INSERT INTO #test
SELECT v.number, o.name, o.type_desc
FROM   sys.all_objects o
       INNER JOIN master..spt_values v
         ON v.type = 'P' AND v.number BETWEEN 1 AND 1000 

Ich fand Kenneths Lösung mit zwei XML PATH - Aufrufen immer noch viel schneller und weniger ressourcenintensiv.

+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+
|                 | CPU Time (Seconds) | Elapsed Time (Seconds) | #test Scan Count | #test Logical Reads | Worktable logical reads | Worktable lob logical reads |
+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+
| Single XML PATH | 51.077             | 15.521                 | 1,005            | 60,165              | 51,161                  | 1,329,207                   |
| Double XML PATH | 3.1720             | 3.010                  | 2,005            | 92,088              | 14,951                  |   233,681                   |
+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+

Für jedes einzelne id in #test Führt es zwei Operationen anstelle einer aus, aber diese Operation ist erheblich billiger als das Erstellen und anschließende Reparieren des XML.

6
Martin Smith

Wie bereits von Martin Smith hervorgehoben, ist ein CLR-Aggregat wahrscheinlich die beste Wahl. Auch hier ist es eine gute Idee, Ihre Ergebnisse in einer temporären Tabelle zu speichern.

Hier ist eine weitere mögliche T-SQL-Implementierung, die UNPIVOT/PIVOT verwendet:

IF OBJECT_ID('tempdb..#test') IS NOT NULL 
    DROP TABLE #test;

CREATE TABLE #test (
    id int,
    name varchar(128),
    car varchar(128)
)

CREATE CLUSTERED INDEX ix ON #test(ID);

INSERT INTO #test
SELECT v.number, o.name, o.type_desc
FROM   sys.all_objects o
       INNER JOIN master..spt_values v
         ON v.type = 'P' AND v.number BETWEEN 1 AND 1000 

;WITH info(col) AS (
    SELECT 'car'
    UNION ALL
    SELECT 'name'
)
SELECT *
FROM info
CROSS JOIN (
    SELECT DISTINCT id
    FROM #test
) AS ids
CROSS APPLY (
    SELECT v = STUFF((
        SELECT ',' + value AS [text()]
        FROM #test
        UNPIVOT (value FOR col IN (name,car)) AS u
        WHERE col = info.col
            AND id = ids.id
            AND value <> ''
        FOR XML PATH(''), type
    ).value('.','varchar(max)'),1,1,SPACE(0))
) AS ca(val)
PIVOT (MIN(val) FOR col IN (car,name)) AS p;

Es läuft ungefähr zur gleichen Zeit wie Kenneths Lösung.

1
spaghettidba

Versuche dies

Verwenden Sie die Funktion Right, um das führende Komma anstelle der Funktionen xml zu entfernen

Verwenden Sie case Anweisungen, um Kommas für Leerzeichen zu vermeiden

SELECT t.id, 
       RIGHT(A.NAME, Len(A.NAME) - 1) AS NAME, 
       RIGHT(A.car, Len(A.car) - 1)   AS car 
FROM   dbo.test t 
       OUTER apply(SELECT (SELECT id, 
                                  CASE WHEN NAME<>'' THEN ',' ELSE '' END + NAME  AS NAME, 
                                  CASE WHEN car<>'' THEN ',' ELSE '' END + car AS car 
                           FROM   test 
                           WHERE  test.id = t.id 
                           FOR xml path(''), type) AS M) A 
GROUP  BY id, 
          RIGHT(A.NAME, Len(A.NAME) - 1), 
          RIGHT(A.car, Len(A.car) - 1) 

Anmerkung : Hier Group by kann auch durch distinct ersetzt werden, da wir keine aggregate -Funktionen verwenden

0