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 个子句 WHERE
: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;
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函数