Funktionsweise von Stuff und 'For Xml Path' in SQL Server

367

Tabelle ist:

+----+------+
| Id | Name |
+----+------+    
| 1  | aaa  |
| 1  | bbb  |
| 1  | ccc  |
| 1  | ddd  |
| 1  | eee  |
+----+------+

Erforderliche Ausgabe:

+----+---------------------+
| Id |        abc          |
+----+---------------------+ 
|  1 | aaa,bbb,ccc,ddd,eee |
+----+---------------------+

Abfrage:

SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Diese Abfrage funktioniert ordnungsgemäß. Aber ich brauche nur die Erklärung, wie es funktioniert oder gibt es einen anderen oder kurzen Weg, dies zu tun.

Ich bin sehr verwirrt, das zu verstehen.

Puneet Chawla
quelle
1
Ich habe dafür eine SqlFiddle-Seite erstellt , damit sie im wirklichen Leben funktioniert. Hoffe es hilft anderen.
Sabuncu
1
^ Vielleicht IDist das in einer anderen Tabelle verschiedener Entitäten einzigartig, und diese Tabelle speichert Dinge, die zu ihnen gehören.
Nick Rolando
Diese Abfrage funktioniert nicht, wenn einige der Zeilen eine andere ID haben. zB wenn 'ddd' und 'eee' Id 2 haben.
KevinVictor
10
Zeit für meinen monatlichen Besuch auf dieser Seite, um zu sehen, wo ich falsch gelaufen bin.
Taylor Ackley

Antworten:

683

So funktioniert es:

1. Holen Sie sich eine XML-Elementzeichenfolge mit FOR XML

Durch Hinzufügen von FOR XML PATH am Ende einer Abfrage können Sie die Ergebnisse der Abfrage als XML-Elemente ausgeben, wobei der Elementname im PATH-Argument enthalten ist. Zum Beispiel, wenn wir die folgende Anweisung ausführen würden:

SELECT ',' + name 
              FROM temp1
              FOR XML PATH ('')

Durch Übergabe einer leeren Zeichenfolge (FOR XML PATH ('')) erhalten wir stattdessen Folgendes:

,aaa,bbb,ccc,ddd,eee

2. Entfernen Sie das führende Komma mit STUFF

Die STUFF-Anweisung "stopft" buchstäblich eine Zeichenfolge in eine andere und ersetzt Zeichen innerhalb der ersten Zeichenfolge. Wir verwenden sie jedoch einfach, um das erste Zeichen der resultierenden Werteliste zu entfernen.

SELECT abc = STUFF((
            SELECT ',' + NAME
            FROM temp1
            FOR XML PATH('')
            ), 1, 1, '')
FROM temp1

Die Parameter von STUFFsind:

  • Die Zeichenfolge, die "gestopft" werden soll (in unserem Fall die vollständige Liste der Namen mit einem führenden Komma)
  • Der Ort, an dem mit dem Löschen und Einfügen von Zeichen begonnen werden soll (1, wir füllen eine leere Zeichenfolge ein)
  • Die Anzahl der zu löschenden Zeichen (1, das führende Komma)

Am Ende haben wir also:

aaa,bbb,ccc,ddd,eee

3. Treten Sie der ID bei, um eine vollständige Liste zu erhalten

Als nächstes fügen wir dies einfach in die Liste der IDs in der temporären Tabelle ein, um eine Liste der IDs mit Namen zu erhalten:

SELECT ID,  abc = STUFF(
             (SELECT ',' + name 
              FROM temp1 t1
              WHERE t1.id = t2.id
              FOR XML PATH (''))
             , 1, 1, '') from temp1 t2
group by id;

Und wir haben unser Ergebnis:

-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

Hoffe das hilft!

FutbolFan
quelle
57
Sie sollten für das Dokumentationsteam von Microsoft (falls vorhanden) arbeiten
Fandango68
55
@ Fandango68, @ FutbolFan - Er kann nicht für das Dokumentationsteam von Microsoft arbeiten. Seine Erklärungen sind zu klar und zu direkt. ;-)
Chris
1
@ ChrisProsser Ich stimme zu. Oracle war Microsoft in dieser LISTAGGHinsicht voraus, indem es Funktionen in Oracle 11gR2 einführte . Ich vermisse diese Funktionalität an Tagen, an denen ich sie stattdessen verwenden muss. techonthenet.com/oracle/functions/listagg.php
FutbolFan
2
Hallo. Wenn Sie in Schritt 1 Folgendes tun: SELECT name FROM temp1 FOR XML PATH ('') ... erhalten Sie <name>aaa </ name> <name> bbb </ name> ... etc ... I didn ' Ich merke das zuerst nicht ... Wenn du es in SELECT '' + name ... etc ... änderst, werden die Tags entfernt.
Kevin Victor
1
@ChrisProsser - Sybase ASA hat seit listJahrzehnten eine Funktion. Leider hat Microsoft SQLServer stattdessen auf Sybases ASE basiert und sich erst letztes Jahr mit einer Listenfunktion beschäftigt. Ich stimme zu - es ist umwerfend. Und dann nennen sie es string_agg. Ich hätte gedacht, listwar ziemlich offensichtlich.
Youcantryreachingme
75

Dieser Artikel behandelt verschiedene Möglichkeiten zum Verketten von Zeichenfolgen in SQL, einschließlich einer verbesserten Version Ihres Codes, die die verketteten Werte nicht XML-codiert.

SELECT ID, abc = STUFF
(
    (
        SELECT ',' + name
        FROM temp1 As T2
        -- You only want to combine rows for a single ID here:
        WHERE T2.ID = T1.ID
        ORDER BY name
        FOR XML PATH (''), TYPE
    ).value('.', 'varchar(max)')
, 1, 1, '')
FROM temp1 As T1
GROUP BY id

Beginnen Sie mit der inneren Abfrage, um zu verstehen, was passiert:

SELECT ',' + name
FROM temp1 As T2
WHERE T2.ID = 42 -- Pick a random ID from the table
ORDER BY name
FOR XML PATH (''), TYPE

Da Sie angeben FOR XML, erhalten Sie eine einzelne Zeile mit einem XML-Fragment, das alle Zeilen darstellt.

Da Sie für die erste Spalte keinen Spaltenalias angegeben haben, wird jede Zeile in ein XML-Element mit dem Namen in Klammern nach dem eingeschlossen FOR XML PATH. Wenn Sie dies beispielsweise getan hätten FOR XML PATH ('X'), würden Sie ein XML-Dokument erhalten, das wie folgt aussieht:

<X>,aaa</X>
<X>,bbb</X>
...

Da Sie jedoch keinen Elementnamen angegeben haben, erhalten Sie nur eine Liste mit Werten:

,aaa,bbb,...

Das .value('.', 'varchar(max)')ruft einfach den Wert aus dem resultierenden XML-Fragment ab, ohne XML-Codierung von "Sonderzeichen". Sie haben jetzt eine Zeichenfolge, die wie folgt aussieht:

',aaa,bbb,...'

Die STUFFFunktion entfernt dann das führende Komma und gibt Ihnen ein Endergebnis, das wie folgt aussieht:

'aaa,bbb,...'

Auf den ersten Blick sieht es ziemlich verwirrend aus, aber im Vergleich zu einigen anderen Optionen ist die Leistung recht gut.

Richard Deeming
quelle
2
Was ist die Verwendung von Typ in Ihrer Abfrage. Ich denke, es für die Definition, das Ergebnis des XML-Pfades wird im Wert gespeichert (nicht sicher erklären, wenn falsch).
Puneet Chawla
8
@PuneetChawla: Die TYPEDirektive weist SQL an, die Daten mit dem xmlTyp zurückzugeben. Ohne sie werden die Daten als zurückgegeben nvarchar(max). Es wird hier verwendet, um XML-Codierungsprobleme zu vermeiden, wenn die nameSpalte Sonderzeichen enthält .
Richard Deeming
2
@barlop: Wie im SimpleTalk-Artikel erläutert, können Sie beim Löschen des TYPEund .value('.', 'varchar(max)')im Ergebnis XML-codierte Entitäten erhalten.
Richard Deeming
1
@RichardDeeming meinst du, ob die Daten spitze Klammern enthalten oder enthalten könnten?
Barlop
1
Da Sie jedoch keinen Elementnamen angegeben haben, erhalten Sie nur eine Liste mit Werten . Dies ist die Erkenntnis, die mir gefehlt hat. Vielen Dank.
Adam
44

Der PATH-Modus wird zum Generieren von XML aus einer SELECT-Abfrage verwendet

1. SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH;  

Ouput:
<row>
<ID>1</ID>
<Name>aaa</Name>
</row>

<row>
<ID>1</ID>
<Name>bbb</Name>
</row>

<row>
<ID>1</ID>
<Name>ccc</Name>
</row>

<row>
<ID>1</ID>
<Name>ddd</Name>
</row>

<row>
<ID>1</ID>
<Name>eee</Name>
</row>

Die Ausgabe ist elementzentriertes XML, bei dem jeder Spaltenwert im resultierenden Rowset in ein Zeilenelement eingeschlossen wird. Da in der SELECT-Klausel keine Aliase für die Spaltennamen angegeben sind, stimmen die generierten untergeordneten Elementnamen mit den entsprechenden Spaltennamen in der SELECT-Klausel überein.

Für jede Zeile im Rowset wird ein Tag hinzugefügt.

2.
SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH('');

Ouput:
<ID>1</ID>
<Name>aaa</Name>
<ID>1</ID>
<Name>bbb</Name>
<ID>1</ID>
<Name>ccc</Name>
<ID>1</ID>
<Name>ddd</Name>
<ID>1</ID>
<Name>eee</Name>

Für Schritt 2: Wenn Sie eine Zeichenfolge mit der Länge Null angeben, wird das Umbruchelement nicht erzeugt.

3. 

    SELECT   

           Name  
    FROM temp1
    FOR XML PATH('');

    Ouput:
    <Name>aaa</Name>
    <Name>bbb</Name>
    <Name>ccc</Name>
    <Name>ddd</Name>
    <Name>eee</Name>

4. SELECT   
        ',' +Name  
FROM temp1
FOR XML PATH('')

Ouput:
,aaa,bbb,ccc,ddd,eee

In Schritt 4 verketten wir die Werte.

5. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1

Ouput:
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee


6. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1 GROUP by iD

Ouput:
ID  abc
1   ,aaa,bbb,ccc,ddd,eee

In Schritt 6 gruppieren wir das Datum nach ID.

STUFF (source_string, start, length, add_string) Parameter oder Argumente source_string Die zu ändernde Quellzeichenfolge. start Die Position in der Quellzeichenfolge, um Längenzeichen zu löschen und dann add_string einzufügen. Länge Die Anzahl der Zeichen, die aus source_string gelöscht werden sollen. add_string Die Zeichenfolge, die an der Startposition in den source_string eingefügt werden soll.

SELECT ID,
    abc = 
    STUFF (
        (SELECT   
                ',' +Name  
        FROM temp1
        FOR XML PATH('')), 1, 1, ''
    )
FROM temp1 GROUP by iD

Output:
-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------
Neha Chopra
quelle
1
Sie schreiben "In Schritt 4 verketten wir die Werte." Aber es ist nicht klar, warum / wie die ','als Spalte angegebene Spalte in Kombination mit dem ('')After-XML-Pfad zu einer Verkettung führt
Barlop
In Schritt 4 wird für jede Zeichenfolgenoperation das angegebene Umhüllungselement verwendet, das für diesen Fall leer ('') ist.
vCillusion
1
Für alle, die sich über Punkt 4 wundern und warum <Name> verschwindet. Dies liegt daran, dass nach der Verkettung Name mit Komma keine Spalte mehr, sondern nur noch Wert vorhanden ist, sodass SQL Server nicht weiß, welcher Name für das XML-Tag verwendet werden soll. Diese Abfrage SELECT 'a' FROM some_table FOR XML PATH('')erzeugt beispielsweise : 'aaaaaaa'. Aber wenn Spaltenname angegeben wird: SELECT 'a' AS Col FROM some_table FOR XML PATH('')Sie erhalten Ergebnis:<Col>a</Col><Col>a</Col><Col>a</Col>
Anth
23

In Azure SQL Database und SQL Server (ab 2017) gibt es sehr neue Funktionen, um genau dieses Szenario zu handhaben. Ich glaube, dies würde als native offizielle Methode für das dienen, was Sie mit der XML / STUFF-Methode erreichen möchten. Beispiel:

select id, STRING_AGG(name, ',') as abc
from temp1
group by id

STRING_AGG - https://msdn.microsoft.com/en-us/library/mt790580.aspx

BEARBEITEN: Als ich dies ursprünglich veröffentlichte, erwähnte ich SQL Server 2016, als ich dachte, ich hätte das bei einer potenziellen Funktion gesehen, die aufgenommen werden sollte. Entweder habe ich mich falsch daran erinnert oder etwas hat sich geändert, danke für die vorgeschlagene Bearbeitung, mit der die Version repariert wurde. Außerdem ziemlich beeindruckt und war mir des mehrstufigen Überprüfungsprozesses, der mich gerade für eine letzte Option angezogen hat, nicht ganz bewusst.

Brian Jorden
quelle
3
STRING_AGG ist nicht in SQL Server 2016 enthalten. Es wird als "vNext" bezeichnet.
N8allan
Hoppla, ich wollte die Bearbeitung von @lostmylogin nicht überschreiben. Tut mir leid ... Das ist derjenige, der die Korrekturbearbeitung tatsächlich durchgesetzt hat.
Brian Jorden
5

In for xml path, wenn wir definieren einen beliebigen Wert wie [ for xml path('ENVLOPE') ]dann werden diese Tags mit jeder Zeile hinzugefügt werden:

<ENVLOPE>
</ENVLOPE>
vikas
quelle
2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Hier in der obigen Abfrage wird die STUFF- Funktion verwendet, um nur das erste Komma (,)aus der generierten XML-Zeichenfolge zu entfernen, (,aaa,bbb,ccc,ddd,eee)dann wird es (aaa,bbb,ccc,ddd,eee).

Und FOR XML PATH('')konvertiert einfach Spaltendaten in (,aaa,bbb,ccc,ddd,eee)Zeichenfolgen, aber in PATH übergeben wir '', sodass kein XML-Tag erstellt wird.

Und am Ende haben wir Datensätze mithilfe der ID- Spalte gruppiert .

Mahendra Singh Dhami
quelle
2

Ich habe debuggt und schließlich meine "ausgestopfte" Abfrage auf normale Weise zurückgegeben.

Einfach

select * from myTable for xml path('myTable')

gibt mir den Inhalt der Tabelle zum Schreiben in eine Protokolltabelle von einem Trigger, den ich debugge.

SlavaTT
quelle
1
Declare @Temp As Table (Id Int,Name Varchar(100))
Insert Into @Temp values(1,'A'),(1,'B'),(1,'C'),(2,'D'),(2,'E'),(3,'F'),(3,'G'),(3,'H'),(4,'I'),(5,'J'),(5,'K')
Select X.ID,
stuff((Select ','+ Z.Name from @Temp Z Where X.Id =Z.Id For XML Path('')),1,1,'')
from @Temp X
Group by X.ID
Omkar Naik
quelle
-1

STUFF ((SELECT different ',' + CAST (T.ID) FROM Table T wobei T.ID = 1 FOR XML PATH ('')), 1,1, '') AS Name

B. Nishan
quelle
-3

Ich benutze häufig mit where-Klausel

SELECT 
TapuAda=STUFF(( 
SELECT ','+TBL.TapuAda FROM (
SELECT TapuAda FROM T_GayrimenkulDetay AS GD 
INNER JOIN dbo.T_AktiviteGayrimenkul AS AG ON  D.GayrimenkulID=AG.GayrimenkulID WHERE 
AG.AktiviteID=262
) AS TBL FOR XML PATH ('')
),1,1,'')
sbaysal
quelle
2
Ich verstehe nicht, wie das eine Antwort ist. Könnten Sie bitte einige Erklärungen abgeben?
Gar