在Microsoft SQL Server 2005中模拟group_concat MySQL函数?


347

我正在尝试将基于MySQL的应用程序迁移到Microsoft SQL Server 2005(不是强制选择,但这是必须的)。

在原始应用程序中,我们几乎完全使用 ANSI-SQL兼容的语句,但有一个明显的例外-我们group_concat相当频繁地使用MySQL的函数。

group_concat顺便说一句,这样做:给一个表,例如,雇员姓名和项目...

SELECT empName, projID FROM project_members;

返回:

ANDY   |  A100
ANDY   |  B391
ANDY   |  X010
TOM    |  A100
TOM    |  A510

……这就是使用group_concat得到的结果:

SELECT 
    empName, group_concat(projID SEPARATOR ' / ') 
FROM 
    project_members 
GROUP BY 
    empName;

返回:

ANDY   |  A100 / B391 / X010
TOM    |  A100 / A510

因此,我想知道的是:是否可以在SQL Server中编写一个模仿的功能的用户定义函数group_concat

我几乎没有使用UDF,存储过程或类似内容的经验,只是直接使用SQL,所以请在过多解释的一边做错:)



这是一个老问题,但是我喜欢这里给出的CLR解决方案
2012年

如何使用SQL查询创建逗号分隔的列表的可能重复项-该职位范围更广,所以我选择该职位为规范
TMS


您怎么知道应该以哪个顺序建立列表,例如,您显示A100 / B391 / X010,但是鉴于关系数据库中没有隐式顺序,因此可以很容易地将其作为X010 / A100 / B391或任何其他组合。
史蒂夫·福特

Answers:


174

没有真正简单的方法可以做到这一点。但是,有很多想法。

我找到的最好的一个

SELECT table_name, LEFT(column_names , LEN(column_names )-1) AS column_names
FROM information_schema.columns AS extern
CROSS APPLY
(
    SELECT column_name + ','
    FROM information_schema.columns AS intern
    WHERE extern.table_name = intern.table_name
    FOR XML PATH('')
) pre_trimmed (column_names)
GROUP BY table_name, column_names;

如果数据中可能包含诸如 <

WITH extern
     AS (SELECT DISTINCT table_name
         FROM   INFORMATION_SCHEMA.COLUMNS)
SELECT table_name,
       LEFT(y.column_names, LEN(y.column_names) - 1) AS column_names
FROM   extern
       CROSS APPLY (SELECT column_name + ','
                    FROM   INFORMATION_SCHEMA.COLUMNS AS intern
                    WHERE  extern.table_name = intern.table_name
                    FOR XML PATH(''), TYPE) x (column_names)
       CROSS APPLY (SELECT x.column_names.value('.', 'NVARCHAR(MAX)')) y(column_names) 

1
该示例对我有用,但是我尝试执行另一种聚合,但没有成功,给了我一个错误:“在FROM子句中多次指定了相关名称'pre_trimmed'。”
PhilChuang

7
'pre_trimmed'只是子查询的别名。别名对于子查询是必需的,并且必须是唯一的,因此对于另一个子查询,请将其更改为唯一的东西...
Koen 2012年

2
您可以显示一个没有table_name作为列名的示例吗,这令人困惑。
S.Mason's

169

我参加聚会可能有点晚,但是这种方法对我有用,并且比COALESCE方法更容易。

SELECT STUFF(
             (SELECT ',' + Column_Name 
              FROM Table_Name
              FOR XML PATH (''))
             , 1, 1, '')

1
这仅显示如何合并值-group_concat按组合并它们,这更具挑战性(以及OP似乎需要的内容)。有关如何执行此操作的信息,请参见SO 15154644的公认答案-WHERE子句是至关重要的补充
DJDave

@DJDave指的是这个答案。另请参见对类似问题的公认答案
约翰·卡明斯

51

可能为时已晚,现在无法受益,但这不是最简单的做事方法吗?

SELECT     empName, projIDs = replace
                          ((SELECT Surname AS [data()]
                              FROM project_members
                              WHERE  empName = a.empName
                              ORDER BY empName FOR xml path('')), ' ', REQUIRED SEPERATOR)
FROM         project_members a
WHERE     empName IS NOT NULL
GROUP BY empName

有趣。我已经完成了手头的项目,但是我将尝试这种方法。谢谢!
DanM 2010年

7
妙招-唯一的问题是带有空格的姓氏将用分隔符替换空格。
Mark Elliot 2010年

我自己遇到了这样的问题,马克。不幸的是,在MSSQL与时俱进并引入GROUP_CONCAT之前,这是我能够针对这里需要的方法而进行的开销最小的方法中的最少的方法。
哈迪曼

谢谢你!这是一个显示其工作方式的SQL 提琴
逃离

42

SQL Server 2017确实引入了新的聚合函数

STRING_AGG ( expression, separator)

连接字符串表达式的值,并在它们之间放置分隔符值。分隔符未添加到字符串的末尾。

串联的元素可以通过追加来排序 WITHIN GROUP (ORDER BY some_expression)

对于2005-2016版本,我通常在接受的答案中使用XML方法。

但是,这在某些情况下可能会失败。例如,如果要连接的数据包含CHAR(29)你看

FOR XML无法序列化数据...,因为它包含XML不允许的字符(0x001D)。

可以处理所有字符的更可靠的方法是使用CLR聚合。但是,使用这种方法对排序后的元素应用排序更为困难。

不能保证分配变量的方法,在生产代码中应避免使用。


现在在Azure SQL中也可以使用它:azure.microsoft.com/en-us/roadmap/…–
Simon_Weaver

34

看一下Github 上的GROUP_CONCAT项目,我想我正是您要搜索的内容:

该项目包含一组SQLCLR用户定义的聚合函数(SQLCLR UDA),这些函数共同提供与MySQL GROUP_CONCAT函数类似的功能。有多种功能可确保根据所需功能实现最佳性能。


2
@MaxiWheat:许多人在点击不投票之前没有仔细阅读问题或答案。由于他们的错误,它直接影响所有者的帖子。
史蒂夫·林

效果很好。唯一的功能,我缺少的是)可以像能力排序在列其中MySQL的GROUP_CONCAT(:GROUP_CONCAT(klascode,'(',name,')' ORDER BY klascode ASC SEPARATOR ', ')

10

要连接具有多个项目经理的项目中的所有项目经理名称,请输入:

SELECT a.project_id,a.project_name,Stuff((SELECT N'/ ' + first_name + ', '+last_name FROM projects_v 
where a.project_id=project_id
 FOR
 XML PATH(''),TYPE).value('text()[1]','nvarchar(max)'),1,2,N''
) mgr_names
from projects_v a
group by a.project_id,a.project_name

9

使用下面的代码,您必须在部署之前在项目属性上设置PermissionLevel = External,然后通过运行“ ALTER DATABASE database_name SET”来更改数据库以信任外部代码(请确保在其他地方阅读有关安全风险和替代方法的信息,例如证书)。值得信赖”。

using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined,
MaxByteSize=8000,
IsInvariantToDuplicates=true,
IsInvariantToNulls=true,
IsInvariantToOrder=true,
IsNullIfEmpty=true)]
    public struct CommaDelimit : IBinarySerialize
{


[Serializable]
 private class StringList : List<string>
 { }

 private StringList List;

 public void Init()
 {
  this.List = new StringList();
 }

 public void Accumulate(SqlString value)
 {
  if (!value.IsNull)
   this.Add(value.Value);
 }

 private void Add(string value)
 {
  if (!this.List.Contains(value))
   this.List.Add(value);
 }

 public void Merge(CommaDelimit group)
 {
  foreach (string s in group.List)
  {
   this.Add(s);
  }
 }

 void IBinarySerialize.Read(BinaryReader reader)
 {
    IFormatter formatter = new BinaryFormatter();
    this.List = (StringList)formatter.Deserialize(reader.BaseStream);
 }

 public SqlString Terminate()
 {
  if (this.List.Count == 0)
   return SqlString.Null;

  const string Separator = ", ";

  this.List.Sort();

  return new SqlString(String.Join(Separator, this.List.ToArray()));
 }

 void IBinarySerialize.Write(BinaryWriter writer)
 {
  IFormatter formatter = new BinaryFormatter();
  formatter.Serialize(writer.BaseStream, this.List);
 }
    }

我已经使用如下查询查询对此进行了测试:

SELECT 
 dbo.CommaDelimit(X.value) [delimited] 
FROM 
 (
  SELECT 'D' [value] 
  UNION ALL SELECT 'B' [value] 
  UNION ALL SELECT 'B' [value] -- intentional duplicate
  UNION ALL SELECT 'A' [value] 
  UNION ALL SELECT 'C' [value] 
 ) X 

并产生:A,B,C,D


9

尝试了这些,但出于我在MS SQL Server 2005中的目的,以下是最有用的,我在xaprb上找到了

declare @result varchar(8000);

set @result = '';

select @result = @result + name + ' '

from master.dbo.systypes;

select rtrim(@result);

正如您所提到的,@ Mark对我来说是空格字符。


我认为引擎并不能真正保证此方法的任何顺序,因为变量是根据执行计划作为数据流进行计算的。到目前为止,它似乎在大多数时间都有效。
phil_w

6

关于哈迪曼(J Hardiman)的答案,如何:

SELECT empName, projIDs=
  REPLACE(
    REPLACE(
      (SELECT REPLACE(projID, ' ', '-somebody-puts-microsoft-out-of-his-misery-please-') AS [data()] FROM project_members WHERE empName=a.empName FOR XML PATH('')), 
      ' ', 
      ' / '), 
    '-somebody-puts-microsoft-out-of-his-misery-please-',
    ' ') 
  FROM project_members a WHERE empName IS NOT NULL GROUP BY empName

顺便说一句,使用“姓氏”是拼写错误还是我在这里不理解这个概念?

无论如何,谢谢大家,因为它节省了我很多时间:)


1
如果您问我的话,答案会不太友好,而且根本没有帮助。
Tim Meers 2012年

1
只是现在看到了......我并不是用一种卑鄙的方式讲这个,当时我对sql server感到非常沮丧(仍然是)。这篇文章的答案实际上确实很有帮助;编辑:为什么顺便说一句没有帮助?它为我带来了成功
user422190 2013年

1

对于在那里的其他Google员工,这是一个非常简单的即插即用解决方案,在与较复杂的解决方案奋斗了一段时间之后,它对我有用:

SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID ) 
                     FROM returns 
                     WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM 
returns t

请注意,我必须将ID转换为VARCHAR才能将其连接为字符串。如果您不必这样做,这是一个更简单的版本:

SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
                     FROM returns 
                     WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM 
returns t

值得一提的是:https//social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of-mysql-in- sql-server?forum = transactsql

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.