如何使用GROUP BY连接SQL Server中的字符串?


373

如何得到:

id       Name       Value
1          A          4
1          B          8
2          C          9

id          Column
1          A:4, B:8
2          C:9

18
这类问题可以通过具有GROUP_CONCAT()聚合功能的MySQL轻松解决,但在Microsoft SQL Server上解决则比较尴尬。请参阅以下SO问题以寻求帮助:“ 如何基于关系针对一个记录获取多个记录?
Bill Karwin,

1
:每个人都使用Microsoft帐户要选上连接一个简单的解决方案connect.microsoft.com/SQLServer/feedback/details/427987/...
延Mühlenhoff

1
您可以使用SQLCLR聚集在这里找到一个替代品,直到T-SQL增强:groupconcat.codeplex.com
奥兰多Colamatteo

Answers:


550

无需CURSOR,WHILE循环或用户定义的功能

只需使用FOR XML和PATH进行创意。

[注意:此解决方案仅适用于SQL 2005及更高版本。原始问题未指定使用的版本。]

CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)

INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)

SELECT 
  [ID],
  STUFF((
    SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) 
    FROM #YourTable 
    WHERE (ID = Results.ID) 
    FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
  ,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID

DROP TABLE #YourTable

6
为什么一个人锁定一个临时表?
艾米B

3
这是我一生中见过的最酷的SQL内容。知道大型数据集是否“快速”吗?它不会像游标一样开始爬行,不是吗?我希望更多的人会投票赞成这种疯狂。
user12861

6
嗯 我只是讨厌它的子查询风格。联接好得多。只是不要以为我可以在此解决方案中利用它。无论如何,我很高兴看到除了我之外这里还有其他喜欢学习类似SQL知识的人。所有人的荣誉:)
Kevin Fairchild

6
一种更简洁的字符串处理方式:STUFF((SELECT','+ [Name] +':'+ CAST([Value] AS VARCHAR(MAX))FROM #YourTable WHERE(ID = Results.ID)FOR XML PATH('')),1,2,'')AS NameValues
Jonathan Sayce

3
只是为了说明我发现的东西。即使在不区分大小写的环境中,查询的.value部分也需要小写。我猜这是因为它是XML,这是区分大小写的
Jaloopa 2013年

136

如果是SQL Server 2017或SQL Server Vnext,SQL Azure,则可以使用string_agg,如下所示:

select id, string_agg(concat(name, ':', [value]), ', ')
    from #YourTable 
    group by id

完美无瑕!
argoo

1
这效果很好,比接受的答案更好。
Jannick Breunis

51

使用XML路径不会像您期望的那样完美地串联起来...它将用“&amp;”替换“&” 还会与<" and "> ... 混在一起...也许还有其他一些不确定的地方...但是您可以尝试一下

我遇到了一个解决方法...您需要替换:

FOR XML PATH('')
)

与:

FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')

...或者NVARCHAR(MAX)如果您使用的是。

为什么地狱没有SQL串联聚合函数?这是一个PITA。


2
我搜寻了网络,寻找不对输出进行编码的最佳方法。非常感谢!这是绝对的答案-直到MS添加对此的适当支持,例如CONCAT()聚合函数。我要做的就是将其放入一个外部应用程序中,该应用程序返回我的串联字段。我不喜欢在我的选择语句中添加嵌套选择。
MikeTeeVee 2013年

我同意,在不使用Value的情况下,如果文本是XML编码的字符,我们会遇到问题。请在我的博客中找到有关SQL Server中分组串联的方案。 blog.vcillusion.co.in/…–
vCillusion

40

我遇到了几个问题,当我试图凯文飞兆半导体的建议转化为工作与包含空格和特殊XML字符(字符串&<>),它被编码。

我的代码的最终版本(不能回答原始问题,但可能对某人有用)如下:

CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)

INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)

SELECT  [ID],
  STUFF((
    SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
    FROM #YourTable WHERE (ID = Results.ID) 
    FOR XML PATH(''),TYPE 
     /* Use .value to uncomment XML entities e.g. &gt; &lt; etc*/
    ).value('.','VARCHAR(MAX)') 
  ,1,2,'') as NameValues
FROM    #YourTable Results
GROUP BY ID

DROP TABLE #YourTable

与其使用空格作为定界符,而不是用逗号替换所有空格,它只是在每个值前添加一个逗号和空格,然后用于STUFF删除前两个字符。

使用TYPE指令可自动处理XML编码。


21

使用Sql Server 2005及更高版本的另一种选择

---- test data
declare @t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert @t select 1125439       ,'CKT','Approved'
insert @t select 1125439       ,'RENO','Approved'
insert @t select 1134691       ,'CKT','Approved'
insert @t select 1134691       ,'RENO','Approved'
insert @t select 1134691       ,'pn','Approved'

---- actual query
;with cte(outputid,combined,rn)
as
(
  select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
  from @t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid

感谢您的输入,我始终喜欢使用CTE和递归CTE解决SQL Server中的问题。这对我来说是一件好事!
gbdavid

是否可以在带有外部Apply的查询中使用它?
在洞中射击

14

http://groupconcat.codeplex.com安装SQLCLR聚合

然后,您可以编写这样的代码以获得所需的结果:

CREATE TABLE foo
(
 id INT,
 name CHAR(1),
 Value CHAR(1)
);

INSERT  INTO dbo.foo
    (id, name, Value)
VALUES  (1, 'A', '4'),
        (1, 'B', '8'),
        (2, 'C', '9');

SELECT  id,
    dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM    dbo.foo
GROUP BY id;

几年前使用它时,该语法比所有“ XML Path”技巧都干净得多,并且效果很好。当选择SQL CLR函数时,强烈建议您这样做。
AFract

12

SQL Server 2005和更高版本允许您创建自己的自定义聚合函数,包括诸如串联之类的操作,请参见链接文章底部的示例。


4
不幸的是,这需要(?)使用CLR程序集..这是另一个要处理的问题:-/

1
仅该示例将CLR用于实际的串联实现,但这不是必需的。您可以使串联聚合函数使用FOR XML,因此至少在以后调用它会更巧妙!
2016年


9

这只是Kevin Fairchild帖子的补充(顺便说一句,非常聪明)。我会把它添加为评论,但我的观点还不够:)

我将这个想法用于我正在研究的观点,但是我要隐瞒的项目包含空格。所以我稍微修改了代码以不使用空格作为分隔符。

再次感谢Kevin的出色解决方法!

CREATE TABLE #YourTable ( [ID] INT, [Name] CHAR(1), [Value] INT ) 

INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'A', 4) 
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'B', 8) 
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (2, 'C', 9) 

SELECT [ID], 
       REPLACE(REPLACE(REPLACE(
                          (SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A 
                           FROM   #YourTable 
                           WHERE  ( ID = Results.ID ) 
                           FOR XML PATH (''))
                        , '</A><A>', ', ')
                ,'<A>','')
        ,'</A>','') AS NameValues 
FROM   #YourTable Results 
GROUP  BY ID 

DROP TABLE #YourTable 

9

一个例子是

在Oracle中,您可以使用LISTAGG聚合函数。

原始记录

name   type
------------
name1  type1
name2  type2
name2  type3

SQL

SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name

造成

name   type
------------
name1  type1
name2  type2; type3

6
看起来不错,但是问题不是关于Oracle的。
user12861 2013年

13
我明白。但是我一直在为Oracle寻找相同的东西,所以我想我会把它放在这里给像我这样的其他人:)
Michal B.

@MichalB。您不是错过了inner语法吗?例如:listagg(type,',')在group(按名称排序)内?
gregory

@gregory:我编辑了答案。我认为我以前的解决方案曾经可以追溯到过去。您建议的当前表格肯定可以使用,谢谢。
Michal B.

1
对于未来的人们-您可以使用自己的答案写一个新问题,以解决诸如不同平台之类的重大差异
Mike M

7

这里经常会问这种问题,解决方案将在很大程度上取决于基本要求:

https://stackoverflow.com/search?q=sql+pivot

https://stackoverflow.com/search?q=sql+concatenate

通常,没有动态sql,用户定义的函数或游标,就没有仅SQL的方式来执行此操作。


2
不对。cyberkiwi使用cte:s的解决方案是纯SQL,没有任何特定于供应商的黑客。
比昂·林德奎斯特

1
在提出问题时,我不会将递归CTE视为可移植性非常好,但是Oracle现在支持它们。最好的解决方案将取决于平台。对于SQL Server,很可能是FOR XML技术或客户CLR聚合。
Cade Roux 2013年

1
所有问题的最终答案? stackoverflow.com/search?q= [任何问题]
Junjun Liu

7

只是为了补充Cade所说的,这通常是前端显示,因此应在此处处理。我知道有时候为文件导出或其他“仅SQL”解决方案之类的事情,用SQL编写100%的内容有时会更容易,但是大多数情况下,这种级联应该在您的显示层中处理。


11
分组是前端显示的东西吗?有许多有效的方案可以将分组结果集中的一列串联起来。
MGOwen '16

5

不需要游标... while循环就足够了。

------------------------------
-- Setup
------------------------------

DECLARE @Source TABLE
(
  id int,
  Name varchar(30),
  Value int
)

DECLARE @Target TABLE
(
  id int,
  Result varchar(max) 
)


INSERT INTO @Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO @Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO @Source(id, Name, Value) SELECT 2, 'C', 9


------------------------------
-- Technique
------------------------------

INSERT INTO @Target (id)
SELECT id
FROM @Source
GROUP BY id

DECLARE @id int, @Result varchar(max)
SET @id = (SELECT MIN(id) FROM @Target)

WHILE @id is not null
BEGIN
  SET @Result = null

  SELECT @Result =
    CASE
      WHEN @Result is null
      THEN ''
      ELSE @Result + ', '
    END + s.Name + ':' + convert(varchar(30),s.Value)
  FROM @Source s
  WHERE id = @id

  UPDATE @Target
  SET Result = @Result
  WHERE id = @id

  SET @id = (SELECT MIN(id) FROM @Target WHERE @id < id)
END

SELECT *
FROM @Target


@marc_s也许更好的批评是应该在表变量上声明PRIMARY KEY。
Amy B

@marc_s在进一步检查中,该文章是虚假的-几乎所有关于没有IO测量的性能讨论。我确实了解了LAG-非常感谢。
Amy

4

让我们变得非常简单:

SELECT stuff(
    (
    select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb 
    FOR XML PATH('')
    )
, 1, 2, '')

替换此行:

select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb

随着您的查询。


3

没有看到任何交叉应用答案,也不需要xml提取。这与Kevin Fairchild撰写的内容略有不同。在更复杂的查询中使用起来更快,更容易:

   select T.ID
,MAX(X.cl) NameValues
 from #YourTable T
 CROSS APPLY 
 (select STUFF((
    SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
    FROM #YourTable 
    WHERE (ID = T.ID) 
    FOR XML PATH(''))
  ,1,2,'')  [cl]) X
  GROUP BY T.ID

1
如果不使用Value,我们可能会遇到文本为XML编码字符的问题
vCillusion

2

如果“分组依据”主要包含一项,则可以通过以下方式显着提高性能:

SELECT 
  [ID],

CASE WHEN MAX( [Name]) = MIN( [Name]) THEN 
MAX( [Name]) NameValues
ELSE

  STUFF((
    SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) 
    FROM #YourTable 
    WHERE (ID = Results.ID) 
    FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
  ,1,2,'') AS NameValues

END

FROM #YourTable Results
GROUP BY ID

假设您不希望列表中有重复的名称,您可能会或可能不会。
jnm2

1

使用替换功能和FOR JSON PATH

SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
 SELECT DEPT, (SELECT ENAME AS [ENAME]
        FROM EMPLOYEE T2
        WHERE T2.DEPT=T1.DEPT
        FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
    FROM EMPLOYEE T1
    GROUP BY DEPT) T3

有关示例数据和更多方法,请单击此处


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.