String_agg for SQL 服务器 2017 年之前

String_agg for SQL Server before 2017

任何人都可以帮助我使此查询适用于 SQL Server 2014 吗?

这适用于 Postgresql,可能适用于 SQL Server 2017。在 Oracle 上它是 listagg 而不是 string_agg

这是SQL:

select 
    string_agg(t.id,',') AS id
from 
    Table t

我在网站上查看了一些应该使用的 xml 选项,但我无法理解。

在 SQL 2017 之前的服务器中,您可以:

select stuff( (select ',' + cast(t.id as varchar(max))
               from tabel t
               for xml path ('')
              ), 1, 1, ''
            );

stuff() 的唯一目的是删除开头的逗号。这项工作由 for xml path.

完成

请注意,对于某些字符,使用 FOR XML PATH 时会对其值进行转义,例如:

SELECT STUFF((SELECT ',' + V.String
              FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
              FOR XML PATH('')),1,1,'');

这个returns下面的字符串:

7 > 5,Salt & pepper,2
lines'

这不太可能。您可以使用 TYPE 解决此问题,然后获取 XML:

的值
SELECT STUFF((SELECT ',' + V.String
              FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
              FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'');

这个returns下面的字符串:

7 > 5,Salt & pepper,2
lines

这将复制以下行为:

SELECT STRING_AGG(V.String,',')
FROM VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String);

当然,有时您可能希望对数据进行分组,但上面并未说明这一点。为此,您需要使用相关子查询。取以下示例数据:

CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
                          GroupID int,
                          SomeCharacter char(1));

INSERT INTO dbo.MyTable (GroupID, SomeCharacter)
VALUES (1,'A'), (1,'B'), (1,'D'),
       (2,'C'), (2,NULL), (2,'Z');

由此需要以下结果:

GroupID Characters
1 A,B,D
2 C,Z

要实现这一点,您需要执行以下操作:

SELECT MT.GroupID,
       STUFF((SELECT ',' + sq.SomeCharacter 
              FROM dbo.MyTable sq
              WHERE sq.GroupID = MT.GroupID --This is your correlated join and should be on the same columns as your GROUP BY
                                            --You "JOIN" on the columns that would have been in the PARTITION BY
              FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID; --I use GROUP BY rather than DISTINCT as we are technically aggregating here

因此,如果您按 2 列分组,那么子查询的 WHERE 将有 2 个子句:WHERE MT.SomeColumn = sq.SomeColumn AND MT.AnotherColumn = sq.AnotherColumn,而您的外部 GROUP BY 将是 GROUP BY MT.SomeColumn, MT.AnotherColumn.


最后,让我们在其中添加一个 ORDER BY,您也在子查询中定义它。例如,假设您想要按字符串聚合中 ID 降序的值对数据进行排序:

SELECT MT.GroupID,
       STUFF((SELECT ',' + sq.SomeCharacter 
              FROM dbo.MyTable sq
              WHERE sq.GroupID = MT.GroupID
              ORDER BY sq.ID DESC --This is identical to the ORDER BY you would have in your OVER clause
              FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID;

将产生以下结果:

GroupID Characters
1 D,B,A
2 Z,C

不出所料,这永远不会像 STRING_AGG 那样高效,因为多次引用 table(如果您需要执行多个聚合,则需要多个子查询) , 但索引良好的 table 将极大地帮助 RDBMS。如果性能确实是个问题,因为您在单个查询中进行多个字符串聚合,那么我建议您需要重新考虑是否需要聚合,或者是时候考虑升级了。