How Stuff and 'For Xml Path' work in Sql Server

后端 未结 9 1314
盖世英雄少女心
盖世英雄少女心 2020-11-21 13:49

Table is:

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

相关标签:
9条回答
  • 2020-11-21 14:04

    This article covers various ways of concatenating strings in SQL, including an improved version of your code which doesn't XML-encode the concatenated values.

    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
    

    To understand what's happening, start with the inner query:

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

    Because you're specifying FOR XML, you'll get a single row containing an XML fragment representing all of the rows.

    Because you haven't specified a column alias for the first column, each row would be wrapped in an XML element with the name specified in brackets after the FOR XML PATH. For example, if you had FOR XML PATH ('X'), you'd get an XML document that looked like:

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

    But, since you haven't specified an element name, you just get a list of values:

    ,aaa,bbb,...
    

    The .value('.', 'varchar(max)') simply retrieves the value from the resulting XML fragment, without XML-encoding any "special" characters. You now have a string that looks like:

    ',aaa,bbb,...'
    

    The STUFF function then removes the leading comma, giving you a final result that looks like:

    'aaa,bbb,...'
    

    It looks quite confusing at first glance, but it does tend to perform quite well compared to some of the other options.

    0 讨论(0)
  • 2020-11-21 14:04

    In for xml path, if we define any value like [ for xml path('ENVLOPE') ] then these tags will be added with each row:

    <ENVLOPE>
    </ENVLOPE>
    
    0 讨论(0)
  • 2020-11-21 14:07

    There is very new functionality in Azure SQL Database and SQL Server (starting with 2017) to handle this exact scenario. I believe this would serve as a native official method for what you are trying to accomplish with the XML/STUFF method. Example:

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

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

    EDIT: When I originally posted this I made mention of SQL Server 2016 as I thought I saw that on a potential feature that was to be included. Either I remembered that incorrectly or something changed, thanks for the suggested edit fixing the version. Also, pretty impressed and wasn't fully aware of the multi-step review process that just pulled me in for a final option.

    0 讨论(0)
  • 2020-11-21 14:08

    Here is how it works:

    1. Get XML element string with FOR XML

    Adding FOR XML PATH to the end of a query allows you to output the results of the query as XML elements, with the element name contained in the PATH argument. For example, if we were to run the following statement:

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

    By passing in a blank string (FOR XML PATH('')), we get the following instead:

    ,aaa,bbb,ccc,ffffd,eee
    

    2. Remove leading comma with STUFF

    The STUFF statement literally "stuffs” one string into another, replacing characters within the first string. We, however, are using it simply to remove the first character of the resultant list of values.

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

    The parameters of STUFF are:

    • The string to be “stuffed” (in our case the full list of name with a leading comma)
    • The location to start deleting and inserting characters (1, we’re stuffing into a blank string)
    • The number of characters to delete (1, being the leading comma)

    So we end up with:

    aaa,bbb,ccc,ffffd,eee
    

    3. Join on id to get full list

    Next we just join this on the list of id in the temp table, to get a list of IDs with name:

    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;
    

    And we have our result:

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

    Hope this helps!

    0 讨论(0)
  • 2020-11-21 14:11
    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
    
    0 讨论(0)
  • 2020-11-21 14:11

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

    0 讨论(0)
提交回复
热议问题