web-dev-qa-db-fra.com

Fonctionnement de Stuff et 'For Xml Path' dans Sql Server

La table 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’explications sur son fonctionnement ou existe-t-il un autre moyen ou un moyen court de le faire?.

Je suis très confus de comprendre cela.

300
Puneet Chawla

Voici comment cela fonctionne:

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

L'ajout de FOR XML PATH à la fin d'une requête permet de générer les résultats de la requête sous forme d'é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:

,aaa,bbb,ccc,ddd,eee

2. Enlever la virgule avec STUFF

L'instruction STUFF littéralement "encapsule" une chaîne dans une autre, en remplaçant les caractères de la première chaîne. Nous l'utilisons toutefois 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 STUFF sont:

  • 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 sommes en train de les insérer dans une chaîne vide)
  • Le nombre de caractères à supprimer (1, étant la virgule en tête)

Donc on se retrouve avec:

aaa,bbb,ccc,ddd,eee

. Rejoignez sur id pour obtenir la liste complète

Ensuite, nous rejoignons simplement ceci dans la liste des identifiants de la table temporaire, pour obtenir une liste des identifiants avec 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!

577
FutbolFan

Cet article couvre diverses manières de concaténer des chaînes en SQL, y compris une version améliorée de votre code qui ne code pas en 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

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

Comme vous n'avez pas spécifié d'alias de colonne pour la première colonne, chaque ligne est encapsulée dans un élément XML dont le nom est indiqué entre crochets après le FOR XML PATH. Par exemple, si vous avez FOR XML PATH ('X'), vous obtiendrez un document XML ressemblant à ceci:

<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,...

La .value('.', 'varchar(max)') récupère simplement la valeur du fragment XML résultant, sans codage XML des caractères "spéciaux". Vous avez maintenant une chaîne qui ressemble à:

',aaa,bbb,...'

La fonction STUFF supprime ensuite la virgule principale et vous donne un résultat final ressemblant à:

'aaa,bbb,...'

Cela semble assez déroutant au premier abord, mais il a tendance à donner de très bons résultats par rapport à certaines des autres options.

66
Richard Deeming

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 les éléments, dans lequel 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.

Une balise est ajoutée pour chaque ligne de l'ensemble de lignes.

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 chaîne_source chaîne source à modifier. Start Position dans la chaîne source pour supprimer les caractères de longueur, puis insérer add_string. Length Nombre de caractères à supprimer de la chaîne source. Add_string La séquence de caractères à insérer dans la chaîne source à 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 |
-----------------------------------
36
Neha Chopra

Azure SQL Database et SQL Server (à compter de 2017) comportent de toutes nouvelles fonctionnalités pour gérer ce scénario précis. Je pense 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: Lors de la publication de cette information, j'ai mentionné SQL Server 2016 car je pensais l'avoir vue sur une fonctionnalité potentielle à inclure. Soit je me suis souvenu de cela de manière incorrecte ou quelque chose a changé, merci pour l'édition suggérée pour réparer la version. En outre, très impressionné et n'était pas pleinement conscient du processus de révision en plusieurs étapes qui m'a amené à une option finale.

17
Brian Jorden

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

<ENVLOPE>
</ENVLOPE>
4
vikas
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 STUFF la fonction est utilisée pour simplement supprimer la première virgule (,) de la chaîne xml générée (,aaa,bbb,ccc,ddd,eee) puis elle deviendra (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 passons '' afin d'éviter la création d'une balise XML.

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

2
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
1
Omkar Naik

J'ai fait le débogage et finalement renvoyé ma requête 'bourrée' à elle de manière normale.

Simplement

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

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

1
SlavaTT