2017年前的sql server的string_agg

Mas*_* Yi 9 sql sql-server string-aggregation

任何人都可以帮助我使这个查询适用于sql server 2014.这是在Postgresql和可能在sql server 2017上工作.在Oracle上它是listagg而不是string_agg.

这是sql:

select 
string_agg(t.id,',') AS id
from Tabel t
Run Code Online (Sandbox Code Playgroud)

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

use*_*983 23

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

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

这将返回以下字符串:

7 > 5,Salt & pepper,2
lines'
Run Code Online (Sandbox Code Playgroud)

这不太可能是我们所希望的。您可以使用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,'');
Run Code Online (Sandbox Code Playgroud)

这将返回以下字符串:

7 > 5,Salt & pepper,2
lines
Run Code Online (Sandbox Code Playgroud)

这将复制以下行为:

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

当然,有时您可能想要对数据进行分组,上面没有演示这一点。要实现此目的,您需要使用相关子查询。取以下样本数据:

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');
Run Code Online (Sandbox Code Playgroud)

由此想要得到以下结果:

组ID 人物
1 甲、乙、丁
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
Run Code Online (Sandbox Code Playgroud)

因此,如果您对 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;
Run Code Online (Sandbox Code Playgroud)

For 将产生以下结果:

组ID 人物
1 D、B、A
2 Z、C

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


Gor*_*off 10

在2017年之前的SQL Server中,您可以执行以下操作:

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

唯一的目的stuff()是删除最初的逗号.这项工作正在完成for xml path.

  • 如果文本包含诸如 `<`、`>` 等字符,这不会给出预期的结果。 (2认同)
  • 为了解决xml实体引用问题,`select stuff((select','+ cast(t.id as varchar(max))from tabel t for xml path(''),TYPE).value('.',' varchar(MAX)'),1,1,'');` (2认同)