连接/聚合string的最佳方式

我正在find一种方法来将不同行中的string聚合成一行。 我正在寻找在许多不同的地方这样做,所以有一个function,以促进这将是很好的。 我已经尝试了使用COALESCEFOR XML解决scheme,但是他们并没有为我裁剪。

string聚合会做这样的事情:

 id | Name Result: id | Names -- - ---- -- - ----- 1 | Matt 1 | Matt, Rocks 1 | Rocks 2 | Stylus 2 | Stylus 

我已经看了CLR定义的聚合函数来代替COALESCEFOR XML ,但显然SQL Azure 支持CLR定义的东西,这对我来说是一个痛苦,因为我知道能够使用它会解决很多问题给我。

是否有任何可能的解决方法,或类似的最佳方法(可能不如CLR最佳,但嘿,我会拿我能得到的),我可以用来聚合我的东西?

最优化的定义可能会有所不同,但以下是如何使用常规Transact SQL连接来自不同行的string,这应该在Azure中正常工作。

 ;WITH Partitioned AS ( SELECT ID, Name, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber, COUNT(*) OVER (PARTITION BY ID) AS NameCount FROM dbo.SourceTable ), Concatenated AS ( SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1 UNION ALL SELECT P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount FROM Partitioned AS P INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1 ) SELECT ID, FullName FROM Concatenated WHERE NameNumber = NameCount 

说明

该方法归结为三个步骤:

  1. 使用OVERPARTITION分组对行进行编号,并根据需要对它们进行sorting以进行串联。 结果是Partitioned CTE。 我们保留每个分区中的行数,以便稍后过滤结果。

  2. 使用recursionCTE( Concatenated )遍历行号( NameNumber列)将Name值添加到FullName列。

  3. 筛选出所有结果,但是具有最高NameNumber

请记住,为了使这个查询是可预测的,你必须定义两个分组(例如,在你的场景中,具有相同ID行被连接)和sorting(我假设你只是在串联之前按字母顺序sortingstring)。

我已经使用以下数据快速testing了SQL Server 2012上的解决scheme:

 INSERT dbo.SourceTable (ID, Name) VALUES (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus'), (3, 'Foo'), (3, 'Bar'), (3, 'Baz') 

查询结果:

 ID FullName ----------- ------------------------------ 2 Stylus 3 Bar, Baz, Foo 1 Matt, Rocks 

使用下面的FOR XML PATH的方法真的很慢吗? Itzik Ben-Gan写道,这种方法在他的T-SQL查询书(在我看来,Ben-Gan先生是一个值得信赖的来源)有很好的performance。

 create table #t (id int, name varchar(20)) insert into #t values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus') select id ,Names = stuff((select ', ' + name as [text()] from #t xt where xt.id = t.id for xml path('')), 1, 2, '') from #tt group by id 

尽pipe@serge答案是正确的,但我比较了他的方式与xmlpath的时间消耗,我发现xmlpath是如此之快。 我会写比较代码,你可以自己检查一下。 这是@serge的方式:

 DECLARE @startTime datetime2; DECLARE @endTime datetime2; DECLARE @counter INT; SET @counter = 1; set nocount on; declare @YourTable table (ID int, Name nvarchar(50)) WHILE @counter < 1000 BEGIN insert into @YourTable VALUES (ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC') SET @counter = @counter + 1; END SET @startTime = GETDATE() ;WITH Partitioned AS ( SELECT ID, Name, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber, COUNT(*) OVER (PARTITION BY ID) AS NameCount FROM @YourTable ), Concatenated AS ( SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1 UNION ALL SELECT P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount FROM Partitioned AS P INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1 ) SELECT ID, FullName FROM Concatenated WHERE NameNumber = NameCount SET @endTime = GETDATE(); SELECT DATEDIFF(millisecond,@startTime, @endTime) --Take about 54 milliseconds 

而这是xmlpath的方式:

 DECLARE @startTime datetime2; DECLARE @endTime datetime2; DECLARE @counter INT; SET @counter = 1; set nocount on; declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5)) WHILE @counter < 1000 BEGIN insert into @YourTable VALUES (@counter, ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC') SET @counter = @counter + 1; END SET @startTime = GETDATE(); set nocount off SELECT t1.HeaderValue ,STUFF( (SELECT ', ' + t2.ChildValue FROM @YourTable t2 WHERE t1.HeaderValue=t2.HeaderValue ORDER BY t2.ChildValue FOR XML PATH(''), TYPE ).value('.','varchar(max)') ,1,2, '' ) AS ChildValues FROM @YourTable t1 GROUP BY t1.HeaderValue SET @endTime = GETDATE(); SELECT DATEDIFF(millisecond,@startTime, @endTime) --Take about 4 milliseconds 

对于我们这些谁发现这一点 并没有使用Azure SQL数据库

PostgreSQL中的STRING_AGG() ,SQL Server 2017和Azure SQL
https://www.postgresql.org/docs/current/static/functions-aggregate.html
https://docs.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql

MySQL中的GROUP_CONCAT()
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat

(感谢@Brianjorden和@milanio的Azure更新)

那么我的旧的不答复已经被正确地删除了(下面留下的),但是如果有将来在这里降临的话,这是一个好消息。 它们也在Azure SQL数据库中实现了STRING_AGG()。 这应该提供本文所要求的确切function,并具有本地和内置的支持。 @hrobky当时曾提到过这是SQL Server 2016的一个特性。

对于OP的请求,使用非常简单:

 select id, STRING_AGG(name, ', ') as names from some_table group by id 

https://msdn.microsoft.com/en-us/library/mt790580.aspx

—旧post:这里没有足够的信誉直接回复@hrobky,但是STRING_AGG看起来不错,但是它目前仅在SQL Server 2016 vNext中可用。 希望它能尽快跟上Azure SQL Datababse。