Comment Stuff et 'For Xml Path' fonctionnent dans Sql Server

367

Le tableau est:

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

Sortie requise:

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

Requete:

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

Cette requête fonctionne correctement. Mais j'ai juste besoin d'expliquer comment cela fonctionne ou existe-t-il un autre moyen ou court pour le faire.

Je suis très confus de comprendre cela.

Puneet Chawla
la source
1
J'ai créé une page SqlFiddle pour cela, pour le voir fonctionner dans la vraie vie. J'espère que cela aide les autres.
Sabuncu
1
^ Peut-être que le IDest unique dans une table différente d'entités différentes, et cette table stocke les choses qui leur appartiennent.
Nick Rolando
Cette requête ne fonctionne pas si certaines des lignes ont un ID différent. par exemple, si «ddd» et «eee» ont l'ID 2.
KevinVictor
10
Il est temps pour ma visite mensuelle sur cette page de voir où je me suis trompé.
Taylor Ackley

Réponses:

683

Voici comment cela fonctionne:

1. Obtenez la chaîne d'élément XML avec FOR XML

L'ajout de FOR XML PATH à la fin d'une requête vous permet de générer les résultats de la requête en tant qu'éléments XML, avec le nom de l'élément contenu dans l'argument PATH. Par exemple, si nous devions exécuter l'instruction suivante:

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

En passant une chaîne vide (FOR XML PATH ('')), nous obtenons ce qui suit à la place:

,aaa,bbb,ccc,ddd,eee

2. Supprimer la virgule principale avec STUFF

L'instruction STUFF "bourre" littéralement une chaîne dans une autre, en remplaçant les caractères de la première chaîne. Nous l'utilisons cependant simplement pour supprimer le premier caractère de la liste de valeurs résultante.

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

Les paramètres de STUFFsont:

  • La chaîne à «bourrer» (dans notre cas, la liste complète des noms avec une virgule)
  • L'emplacement pour commencer à supprimer et à insérer des caractères (1, nous remplissons une chaîne vide)
  • Le nombre de caractères à supprimer (1, étant la virgule principale)

On se retrouve donc avec:

aaa,bbb,ccc,ddd,eee

3. Rejoignez id pour obtenir la liste complète

Ensuite, nous venons de joindre cela sur la liste des identifiants dans la table temporaire, pour obtenir une liste des identifiants avec le nom:

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;

Et nous avons notre résultat:

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

J'espère que cela t'aides!

FutbolFan
la source
57
Vous devriez travailler pour l'équipe de documentation de Microsoft (le cas échéant)
Fandango68
55
@ Fandango68, @ FutbolFan - Il ne peut pas travailler pour l'équipe de documentation de Microsoft. Ses explications sont trop claires et trop directes. ;-)
Chris
1
@ChrisProsser Je suis d'accord. Oracle a devancé Microsoft à cet LISTAGGégard en introduisant une fonction dans Oracle 11gR2. Je manque cette fonctionnalité les jours où je dois l'utiliser à la place. techonthenet.com/oracle/functions/listagg.php
FutbolFan
2
Bonjour. À l'étape 1, si vous le faites: SÉLECTIONNEZ le nom DE TEMP1 POUR LE CHEMIN XML ('') ... vous obtenez <name>aaa</name> <name> bbb </name> ... etc ... I didn ' t réaliser cela au début ... Le changer en SELECT '' + nom ... etc ... supprime les balises.
KevinVictor
1
@ChrisProsser - Sybase ASA a une listfonction depuis des décennies. Malheureusement, Microsoft a basé SQLServer sur ASE de Sybase à la place, et n'a jamais pris la peine d'utiliser une fonction de liste jusqu'à l'année dernière. Je suis d'accord - c'est ahurissant. Et puis ils le font, ils l'appellent string_agg. J'aurais pensé que listc'était assez évident.
youcantryreachingme
76

Cet article couvre différentes façons de concaténer des chaînes dans SQL, y compris une version améliorée de votre code qui ne code pas XML les valeurs concaténées.

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

Pour comprendre ce qui se passe, commencez par la requête interne:

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

Parce que vous spécifiez FOR XML, vous obtiendrez une seule ligne contenant un fragment XML représentant toutes les lignes.

Étant donné que vous n'avez pas spécifié d'alias de colonne pour la première colonne, chaque ligne sera encapsulée dans un élément XML avec le nom spécifié entre crochets après le FOR XML PATH . Par exemple, si vous l'aviez FOR XML PATH ('X'), vous obtiendrez un document XML qui ressemblerait à:

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

Mais, puisque vous n'avez pas spécifié de nom d'élément, vous obtenez simplement une liste de valeurs:

,aaa,bbb,...

le .value('.', 'varchar(max)') récupère simplement la valeur du fragment XML résultant, sans encoder XML aucun caractère "spécial". Vous avez maintenant une chaîne qui ressemble à:

',aaa,bbb,...'

le STUFF fonction supprime ensuite la virgule de tête, vous donnant un résultat final qui ressemble à:

'aaa,bbb,...'

Cela semble assez déroutant à première vue, mais il a tendance à fonctionner assez bien par rapport à certaines des autres options.

Richard Deeming
la source
2
À quoi sert Type dans votre requête. Je pense que pour la définition, le résultat du chemin XML sera stocké en valeur (je ne suis pas sûr de l'expliquer s'il est incorrect).
Puneet Chawla
8
@PuneetChawla: La TYPEdirective indique à SQL de renvoyer les données en utilisant le xmltype. Sans cela, les données sont renvoyées sous forme de fichier nvarchar(max). Il est utilisé ici pour éviter les problèmes d'encodage XML s'il y a des caractères spéciaux dans la namecolonne.
Richard Deeming
2
@barlop: Comme l' explique l' article SimpleTalk , si vous supprimez le TYPEet .value('.', 'varchar(max)'), vous pouvez vous retrouver avec des entités codées XML dans le résultat.
Richard Deeming
1
@RichardDeeming voulez-vous dire si les données contiennent ou pourraient contenir des crochets angulaires?
barlop
1
Mais, puisque vous n'avez pas spécifié de nom d'élément, vous obtenez simplement une liste de valeurs , c'est la perspicacité qui me manquait. Je vous remercie.
Adam
44

Le mode PATH est utilisé pour générer du XML à partir d'une requête SELECT

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>

La sortie est un XML centré sur l'élément où chaque valeur de colonne de l'ensemble de lignes résultant est encapsulée dans un élément de ligne. Étant donné que la clause SELECT ne spécifie aucun alias pour les noms de colonne, les noms d'élément enfant générés sont les mêmes que les noms de colonne correspondants dans la clause SELECT.

Pour chaque ligne de l'ensemble de lignes, une balise est ajoutée.

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>

Pour l'étape 2: si vous spécifiez une chaîne de longueur nulle, l'élément d'habillage n'est pas produit.

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

À l'étape 4, nous concaténons les valeurs.

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

À l'étape 6, nous regroupons la date par ID.

STUFF (source_string, start, length, add_string) Paramètres ou arguments source_string La chaîne source à modifier. start La position dans la chaîne_source pour supprimer les caractères de longueur, puis insérer la chaîne add_string. longueur Le nombre de caractères à supprimer de chaîne_source. add_string La séquence de caractères à insérer dans la source_string à la position de départ.

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
la source
1
Vous écrivez «À l'étape 4, nous concaténons les valeurs». Mais on ne sait pas pourquoi / comment la ','colonne as spécifiée, combinée au ('')chemin après XML, provoque la concaténation
barlop
À l'étape 4, toute opération de chaîne utilisera l'élément d'habillage spécifié qui est vide ('') dans ce cas.
vCillusion
2
Pour tous ceux qui s'interrogent sur le point 4 et pourquoi <Name> disparaît. C'est parce qu'après la concaténation du nom avec une virgule, il n'y a plus de colonne mais juste une valeur, donc SQL Server ne sait pas quel nom pour la balise xml doit être utilisé. Par exemple , cette requête SELECT 'a' FROM some_table FOR XML PATH('')produira: 'aaaaaaa'. Mais si le nom de la colonne sera spécifié: SELECT 'a' AS Col FROM some_table FOR XML PATH('')vous obtenez le résultat:<Col>a</Col><Col>a</Col><Col>a</Col>
anth
23

Il existe de toutes nouvelles fonctionnalités dans Azure SQL Database et SQL Server (à partir de 2017) pour gérer ce scénario exact. Je crois que cela servirait de méthode officielle native pour ce que vous essayez d'accomplir avec la méthode XML / STUFF. Exemple:

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

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

EDIT: Lorsque j'ai initialement publié cela, j'ai fait mention de SQL Server 2016 car je pensais l'avoir vu sur une fonctionnalité potentielle qui devait être incluse. Soit je m'en suis souvenu incorrectement, soit quelque chose a changé, merci pour la modification suggérée corrigeant la version. En outre, assez impressionné et n'était pas pleinement au courant du processus d'examen en plusieurs étapes qui m'a simplement amené à une option finale.

Brian Jorden
la source
3
STRING_AGG n'est pas dans SQL Server 2016. Il est censé venir dans "vNext".
N8allan
Oups, je ne voulais pas écraser la modification de @lostmylogin désolé à ce sujet ... C'est qui a réellement poussé à travers la correction.
Brian Jorden
5

Dans for xml path, si nous définissons une valeur comme celle- [ for xml path('ENVLOPE') ]ci, ces balises seront ajoutées à chaque ligne:

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

Ici, dans la requête ci-dessus, la fonction STUFF est utilisée pour simplement supprimer la première virgule (,)de la chaîne xml générée, (,aaa,bbb,ccc,ddd,eee)elle deviendra alors (aaa,bbb,ccc,ddd,eee).

Et FOR XML PATH('')convertit simplement les données de colonne en (,aaa,bbb,ccc,ddd,eee)chaîne, mais dans PATH, nous transmettons '' afin qu'il ne crée pas de balise XML.

Et à la fin, nous avons regroupé les enregistrements en utilisant colonne ID .

Mahendra Singh Dhami
la source
2

J'ai fait le débogage et j'ai finalement renvoyé ma requête "bourrée" comme d'habitude.

Simplement

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

me donne le contenu de la table à écrire dans une table de journal à partir d'un déclencheur que je débogue.

SlavaTT
la source
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
la source
-1

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

B.Nishan
la source
-3

Im utilisant fréquemment la clause where

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
la source
2
Je ne vois pas en quoi c'est une réponse, pourriez-vous jeter quelques explications s'il vous plaît?
Gar