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。如果性能确实是个问题,因为您在单个查询中进行多个字符串聚合,那么我建议您需要重新考虑是否需要聚合,或者是时候考虑升级了。
任何人都可以帮助我使此查询适用于 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。如果性能确实是个问题,因为您在单个查询中进行多个字符串聚合,那么我建议您需要重新考虑是否需要聚合,或者是时候考虑升级了。