表:

Id Name
1 aaa
1 bbb
1 ccc
1 ddd
1 eee

要求输出:

Id abc
1 aaa,bbb,ccc,ddd,eee

查询:

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

该查询工作正常。但我只是需要解释一下它是如何工作的,或者是否有其他或简单的方法来做到这一点。

我有点搞不懂了。


当前回答

本文介绍了在SQL中连接字符串的各种方法,包括对连接的值不进行xml编码的代码的改进版本。

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

要理解发生了什么,从内部查询开始:

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

因为您指定了FOR XML,所以您将得到一个包含表示所有行的XML片段的单行。

因为没有为第一列指定列别名,所以每一行都被包装在一个XML元素中,在for XML PATH后面的方括号中指定了名称。例如,如果你有For XML PATH ('X'),你会得到一个看起来像这样的XML文档:

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

但是,因为你没有指定一个元素名称,你只是得到一个值列表:

,aaa,bbb,...

. value('。', 'varchar(max)')只是从结果XML片段中检索值,而不进行XML编码任何“特殊”字符。你现在有一个看起来像这样的字符串:

',aaa,bbb,...'

然后STUFF函数删除前导逗号,给出的最终结果如下所示:

'aaa,bbb,...'

乍一看,它看起来相当混乱,但与其他一些选项相比,它确实表现得相当好。

其他回答

本文介绍了在SQL中连接字符串的各种方法,包括对连接的值不进行xml编码的代码的改进版本。

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

要理解发生了什么,从内部查询开始:

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

因为您指定了FOR XML,所以您将得到一个包含表示所有行的XML片段的单行。

因为没有为第一列指定列别名,所以每一行都被包装在一个XML元素中,在for XML PATH后面的方括号中指定了名称。例如,如果你有For XML PATH ('X'),你会得到一个看起来像这样的XML文档:

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

但是,因为你没有指定一个元素名称,你只是得到一个值列表:

,aaa,bbb,...

. value('。', 'varchar(max)')只是从结果XML片段中检索值,而不进行XML编码任何“特殊”字符。你现在有一个看起来像这样的字符串:

',aaa,bbb,...'

然后STUFF函数删除前导逗号,给出的最终结果如下所示:

'aaa,bbb,...'

乍一看,它看起来相当混乱,但与其他一些选项相比,它确实表现得相当好。

我做了调试,最终返回我的'填充'查询到它的正常方式。

简单的

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

给出了要从我调试的触发器写入日志表的表的内容。

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

Azure SQL数据库和SQL Server(从2017年开始)中有非常新的功能来处理这种确切的场景。我相信这可以作为您试图用XML/STUFF方法完成的工作的本地官方方法。例子:

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

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

编辑:当我最初发布这篇文章时,我提到了SQL Server 2016,因为我认为我在一个潜在的功能上看到了它。要么是我记错了,要么是发生了什么变化,感谢修改版本的建议编辑。此外,我印象非常深刻,并没有完全意识到让我做出最终选择的多步骤审查过程。

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

在上面的查询中,STUFF函数用于从生成的xml字符串(,aaa,bbb,ccc,ddd,eee)中删除第一个逗号(,),然后它将变成(aaa,bbb,ccc,ddd,eee)。

而FOR XML PATH(")只是将列数据转换为(,aaa,bbb,ccc,ddd,eee)字符串,但在PATH中我们传递的是",因此它不会创建XML标记。

最后,我们使用ID列对记录进行分组。