2017 年之前 SQL Server 的 String_agg

Posted

技术标签:

【中文标题】2017 年之前 SQL Server 的 String_agg【英文标题】:String_agg for SQL Server before 2017 【发布时间】:2018-08-27 21:39:17 【问题描述】:

谁能帮我使这个查询适用于 SQL Server 2014?

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

这里是 SQL:

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

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

【问题讨论】:

How to make a query with group_concat in sql server的可能重复 【参考方案1】:

注意,对于某些字符,在使用FOR XML PATH时,值会被转义,例如:

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

这将返回以下字符串:

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,'');

这将返回以下字符串:

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 列上进行分组,那么您的子查询将有 2 个子句 WHEREWHERE 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;

For 会产生以下结果:

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

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

【讨论】:

这很棒。谢谢【参考方案2】:

在 SQL Server pre-2017 中,您可以:

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

stuff() 的唯一目的是删除初始逗号。工作由for xml path完成。

【讨论】:

如果文本包含 <> 等字符,这不会给出预期的结果。 解决 xml 实体引用问题,select stuff( (select ',' + cast(t.id as varchar(max)) from tabel t for xml path (''), TYPE ).value('.', 'varchar(MAX)'), 1, 1, '' ); @TT。 . . .这确实假设id 是数字,这就是为什么有一个明确的cast() @GordonLinoff 我或多或少地偏离了标题,寻找最广泛意义上的 string_agg 的替代品。就个人而言,我在所有情况下都使用 TYPE + 值构造,就像一种模板一样。 是否也有模仿“组内”功能的方法?

以上是关于2017 年之前 SQL Server 的 String_agg的主要内容,如果未能解决你的问题,请参考以下文章

Microsoft SQL Server Reporting Services 2017注册表

MS SQL Server的LTRIM,RTRIM和TRIM函数

SQL Server:列出范围之间的月份

在Linux上升级回滚和卸载SQL Server 2017

插入前Microsoft SQL Server转换中的存储过程

20180928 SQL SERVER 基本函数--时间