274

如何獲得:如何使用GROUP BY連接SQL Server中的字符串?

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

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

這種類型的問題就解決了使用它的`GROUP_CONCAT()`聚合函數很容易在MySQL上解決,但在Microsoft SQL Server上解決這個問題則更加棘手。請參閱以下問題尋求幫助: 「[如何根據關係獲取多個記錄對一個記錄?](http://stackoverflow.com/questions/102317/how-to-get-multiple-records-against-one - 基於記錄的關係)「 – 2008-11-07 19:21:41

+1

每個使用微軟賬戶的人都應該投票選擇一個更簡單的連接解決方​​案:https://connect.microsoft.com/SQLServer/feedback/details/427987/olap-function-for-string -concatenation – 2015-07-31 10:45:55

+1

您可以使用此處找到的SQLCLR聚合作爲替代,直到T-SQL得到增強:http://groupconcat.codeplex.com – 2016-03-19 03:29:57

回答

457

沒有光標,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 
5

這類問題是經常在這裏問,該解決方案將依賴大量的潛在需求:

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

and

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

通常,在沒有動態sql,用戶定義的函數或遊標的情況下,不存在僅使用SQL的方法。

+1

不正確。 cyberkiwi使用cte:s的解決方案是純粹的sql,沒有任何廠商特定的hackery。 – 2013-07-25 11:24:04

+1

在提問和回答時,我不會將遞歸CTE計算爲非常便攜,但現在由Oracle支持。最好的解決方案將取決於平臺。對於SQL Server,它很可能是FOR XML技術或客戶CLR聚合。 – 2013-07-25 15:11:32

+1

所有問題的最終答案? http://stackoverflow.com/search?q=[無論什麼問題] – 2016-12-08 11:07:12

6

只是爲了增加凱德所說的,這通常是一個前端顯示的東西,因此應該在那裏處理。我知道,有時在SQL中爲文件導出或其他「僅SQL」解決方案寫入100%的東西比較容易,但大多數情況下,這種串聯應該在顯示層處理。

+7

分組現在是一個前端顯示的東西?連接分組結果集中的一列有很多有效方案。 – MGOwen 2016-08-03 03:21:14

4

不需要光標... 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 
+0

請參閱:[不良習慣踢:思考WHILE循環不是遊標](http://sqlblog.com/blogs/aaron_bertrand/archive/2012/01/26/the-fallacy-that-a-while- loop-isn-ta-cursor.aspx) – 2015-03-09 19:17:38

+0

@marc_s也許更好的批評是PRIMARY KEY應該在表變量上聲明。 – 2015-03-10 02:19:21

+0

@marc_s在進一步的檢查中,這篇文章是一個虛假 - 幾乎所有關於沒有IO測量的性能討論。我確實瞭解LAG - 非常感謝。 – 2015-03-10 04:34:45

11

SQL Server 2005和更高版本允許你創建自己的custom aggregate functions,包括搞什麼concatenation-看到樣品在鏈接的文章的底部。

18

使用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 
4

這僅僅是一個除了凱文飛兆半導體後(順便說一句非常聰明)的另一種選擇。我會添加它作爲評論,但我還沒有足夠的積分:)

我正在使用這個想法我正在處理的視圖,但是我concudyating項目包含空格。所以我稍微修改了代碼,不使用空格作爲分隔符。

再次感謝非常酷的解決方法凱文!

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 
32

我遇到了幾個問題,當我試圖轉換凱文飛兆半導體的建議包含空格和被編碼的特殊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刪除前兩個字符。

XML編碼通過使用TYPE指令自動處理。

40

使用XML路徑將不會像您所期望的那樣完美連接......它會將「&」替換爲「& amp」並且還將惹<" and "> ......也許其他一些事情,不知道...但你可以試試這個

我碰到一個解決辦法來爲這個......你需要更換:

FOR XML PATH('') 
) 

有:

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

...或者NVARCHAR(MAX)如果那是你使用的是什麼。

爲什麼地獄不SQL有一個連接集合函數?這是一個PITA。

7

在Oracle中,您可以使用LISTAGG聚合函數。 一個例子是:

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

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

會導致:

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

讓我們非常簡單:

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 

與您查詢。

1

可以提高性能顯著以下方式,如果組由主要包含了一個項目:

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 
10

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; 
6

八年後...... Microsoft SQL Server vNext數據庫引擎終於增強了Transact-SQL以直接支持ort分組字符串連接。社區技術預覽版本1.0添加了STRING_AGG函數,並且CTP 1.1爲STRING_AGG函數添加了WITHIN GROUP子句。

參考:https://msdn.microsoft.com/en-us/library/mt775028.aspx

0

沒有看到任何跨應用的答案,也沒有必要對XML提取。這是Kevin Fairchild寫的一個稍微不同的版本。它的速度更快和更容易使用更復雜的查詢:如果是SQL服務器2017年或SQL Server Vnext,SQL Azure中您可以使用如下string_agg

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 
10

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