SQL Server'da dizeleri birleştirmek için GROUP BY nasıl kullanılır?


373

Nasıl alabilirim:

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

için

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

18
Bu tür bir sorun, GROUP_CONCAT()toplama işlevi ile MySQL'de kolayca çözülür , ancak Microsoft SQL Server'da çözmek daha gariptir. Yardım için aşağıdaki SO sorusuna bakın: " İlişkiye dayalı bir kayıtta birden çok kayıt nasıl elde edilir? "
Bill Karwin

1
Microsoft hesabı olan herkes connect
Jens Mühlenhoff

1
: Sen SQLCLR Agregalar T-SQL geliştirilmiş kadar yedek olarak burada bulunan kullanabilirsiniz groupconcat.codeplex.com
Orlando Colamatteo

Yanıtlar:


550

CURSOR, WHILE döngüsü veya Kullanıcı Tanımlı İşlev gerekmez .

FOR XML ve PATH ile yaratıcı olmanız yeterlidir.

[Not: Bu çözüm yalnızca SQL 2005 ve sonraki sürümlerinde çalışır. Orijinal soru kullanılan sürümü belirtmedi.]

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
neden geçici bir masa yok?
Amy B

3
Bu hayatımda gördüğüm en havalı SQL şey. Büyük veri kümeleri için "hızlı" olup olmadığı hakkında bir fikrin var mı? İmleç gibi bir şey taramaya başlamıyor, değil mi? Keşke daha fazla insan bu çılgınlığı oylayabilseydi.
user12861

6
Eh. Sadece alt sorgu stilinden nefret ediyorum. JOINS çok daha hoş. Bunu bu çözümde kullanabileceğimi düşünmeyin. Her neyse, böyle şeyler öğrenmeyi seven benden başka burada başka SQL görevleri olduğunu görmekten memnunum. Kudos to you all :)
Kevin Fairchild

6
Dize manipülasyonu yapmanın biraz daha temiz bir yolu: #SAYINIZ NEREDEN (ID = Results.ID) XML İÇİN STUFF ((SELECT ',' + [Name] + ':' + CAST ([Değer] VARCHAR (MAX) OLARAK) PATH ('')), 1,2, '') AS NameValues
Jonathan Sayce

3
Sadece bulduğum bir şeyi not etmek için. Büyük / küçük harfe duyarlı olmayan bir ortamda bile, sorgunun .value kısmı küçük harf olmalıdır. Sanırım bunun nedeni büyük / küçük harfe duyarlı XML olması
Jaloopa

136

SQL Server 2017 veya SQL Server Vnext, SQL Azure ise string_agg'yi aşağıdaki gibi kullanabilirsiniz:

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

Kusursuz çalışır!
argoo

1
Bu harika çalışıyor, kabul edilen cevaptan daha iyi.
Jannick Breunis

51

XML yolunu kullanmak beklediğiniz gibi mükemmel bir şekilde birleştirilmez ... "&" yerine "& amp;" ve aynı zamanda<" and "> ... belki başka birkaç şeyle uğraşacak, emin değilim ... ama bunu deneyebilirsiniz

Bunun için bir çözüm buldum ... değiştirmeniz gerekiyor:

FOR XML PATH('')
)

ile:

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

...veya NVARCHAR(MAX) da kullandığın şey buysa.

neden cehennemde SQLbirleştirilmiş bir toplama işlevi yok? Bu bir PITA.


2
Ben çıktı kodlamak DEĞİL en iyi yolu arayan net ovuşturduk. Çok teşekkür ederim! Bu kesin yanıttır - MS bunun için bir CONCAT () toplama işlevi gibi uygun destek ekleyene kadar. Ne yaptığım bu benim bitmiş alan döndüren bir dış-uygulama içine atmak olduğunu. Ben select-deyimleri içine yuvalanmış-sel ekleme hayranı değilim.
MikeTeeVee

Değer kullanmadan, metnin XML kodlu bir karakter olduğu problemlerle karşılaşabileceğimizi kabul ettim. Lütfen SQL sunucusunda gruplandırılmış birleştirme senaryolarını içeren blogumu bulun. blog.vcillusion.co.in/…
vCillusion

40

Ben Boşluk içeren dizeleri ve özel XML karakterleri (çalışmak Kevin Fairchild'ın öneri dönüştürme çalıştığında sorunların bir çift koştu &, <,> kodlanmış edildi).

Kodumun son sürümü (orijinal soruya cevap vermeyen, ancak birileri için yararlı olabilir) şöyle görünür:

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

Bir alanı sınırlayıcı olarak kullanmak ve tüm boşlukları virgülle değiştirmek yerine, her değere bir virgül ve boşluk ekler. STUFF ilk iki karakteri kaldırmak için .

XML kodlaması, TYPE yönergesi kullanılarak otomatik olarak halledilir .


21

Sql Server 2005 ve sonraki sürümlerini kullanan başka bir seçenek

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

Giriş için teşekkürler, her zaman SQL sunucusundaki sorunları çözmek için CTE'leri ve Yinelemeli CTE'leri kullanmayı tercih ederim. Bu benim için harika bir işe yaradı!
gbdavid

dış uygulama ile bir sorguda kullanmak mümkün mü?
delikte ateş

14

SQLCLR Agregalarını http://groupconcat.codeplex.com adresinden yükleyin.

Sonra istediğiniz sonucu almak için böyle bir kod yazabilirsiniz:

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;

Birkaç yıl önce kullandım, sözdizimi tüm "XML Yolu" numaralarından çok daha temiz ve çok iyi çalışıyor. SQL CLR işlevleri bir seçenek olduğunda şiddetle tavsiye ederim.
AFract

12

SQL Server 2005 ve sonraki sürümler , birleştirme gibi şeyler de dahil olmak üzere kendi özel toplama işlevlerinizi oluşturmanıza izin verir . Bağlantılı makalenin altındaki örneğe bakın.


4
Ne yazık ki bu (?) CLR meclisleri kullanarak gerektirir .. ki başa çıkmak için başka bir konu: - /

1
Yalnızca örnek, gerçek birleştirme uygulaması için CLR kullanır, ancak bu gerekli değildir. Birleştirme toplama işlevinin XML için kullanılmasını sağlayabilirsiniz, bu yüzden en azından gelecekte çağırmak daha temizdir!
Shiv

12

Sekiz yıl sonra ... Microsoft SQL Server vNext Veritabanı Altyapısı nihayet Transact-SQL'i gruplandırılmış dize birleştirmeyi doğrudan destekleyecek şekilde geliştirdi. Topluluk Teknik Önizleme sürüm 1.0, STRING_AGG işlevini ve CTP 1.1, STRING_AGG işlevi için WITHIN GROUP yan tümcesini ekledi.

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


9

Bu, Kevin Fairchild'in gönderisine sadece bir ektir (bu arada çok zekice). Yorum olarak eklerdim, ama henüz yeterli puanım yok :)

Bu fikri üzerinde çalıştığım bir görüş için kullanıyordum, ancak bir araya getirdiğim öğeler boşluk içeriyordu. Bu yüzden boşlukları sınırlayıcı olarak kullanmamak için kodu biraz değiştirdim.

Serin geçici çözüm için tekrar teşekkürler 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

Bir örnek

Oracle'da LISTAGG toplama işlevini kullanabilirsiniz.

Orijinal kayıtlar

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

sQL

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

Sonuçlanmak

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

6
Güzel görünüyor, ancak sorular özellikle Oracle ile ilgili değil.
user12861

13
Anlıyorum. Ama aynı şeyi Oracle için arıyordum, bu yüzden buraya benim gibi diğer insanlar için koyacağımı düşündüm :)
Michal B.

@MichalB. İçindeki sözdizimini kaçırmıyor musunuz? ör .: grup içinde listagg (tip, ',') (ada göre sırala)?
gregory

@gregory: Cevabımı düzenledim. Eski çözümümün eskiden işe yaradığını düşünüyorum. Önerdiğiniz mevcut form kesinlikle işe yarar, teşekkürler.
Michal B.

1
gelecekteki insanlar için - farklı bir platform gibi önemli bir fark için kendi cevabınızla yeni bir soru yazabilirsiniz
Mike M

7

Bu tür bir soru burada çok sık soruluyor ve çözüm, temel gereksinimlere çok bağlı olacak:

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

ve

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

Genellikle, bunu dinamik sql, kullanıcı tanımlı bir işlev veya imleç olmadan yapmanın yalnızca SQL yolu yoktur.


2
Doğru değil. cyberkiwi'nin cte: s kullanarak çözümü, satıcıya özgü herhangi bir hackery olmadan saf sql.
Björn Lindqvist

1
Soru ve cevap zamanında, özyinelemeli CTE'leri çok taşınabilir olarak saymazdım, ancak şimdi Oracle tarafından destekleniyorlar. En iyi çözüm platforma bağlı olacaktır. SQL Server için büyük olasılıkla FOR XML tekniği veya bir müşteri CLR toplamıdır.
Cade Roux

1
soruların nihai cevabı? stackoverflow.com/search?q=[herhangi bir soru]
Junchen Liu

7

Sadece Cade'nin söylediklerine eklemek için, bu genellikle bir ön uç görüntüleme şeyidir ve bu nedenle orada ele alınmalıdır. Bazen dosya dışa aktarma veya diğer "yalnızca SQL" çözümleri gibi şeyler için SQL'de% 100 bir şey yazmak daha kolaydır, ancak çoğu zaman bu birleştirme ekran katmanınızda ele alınmalıdır.


11
Gruplama şimdi bir ön uç görüntüleme işi mi? Gruplandırılmış bir sonuç kümesinde bir sütunu birleştirmek için birçok geçerli senaryo vardır.
MGOwen

5

İmlece gerek yok ... while döngüsü yeterlidir.

------------------------------
-- 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 belki daha iyi bir eleştiri tablo değişkenleri üzerinde PRIMARY KEY bildirilmelidir.
Amy B

@marc_s Daha fazla incelemede, bu makale - IO ölçümü olmadan neredeyse tüm performans tartışmaları gibi bir sahte. LAG hakkında bilgi edindim - bunun için teşekkürler.
Amy B

4

Çok basit olalım:

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

Bu satırı değiştirin:

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

Sorgunuzla.


3

herhangi bir çapraz uygulama cevapları görmedim, ayrıca xml ekstraksiyonuna gerek yok. İşte Kevin Fairchild'in yazdıklarının biraz farklı bir versiyonu. Daha karmaşık sorgularda kullanımı daha hızlı ve kolaydır:

   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
Değer kullanmadan, metnin XML kodlu bir karakter olduğu durumlarda karşılaşabiliriz
vCillusion

2

Gruplandırma çoğunlukla tek bir öğe içeriyorsa performansı önemli ölçüde aşağıdaki şekilde artırabilirsiniz:

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

Listede, isteyip istemediğiniz, yinelenen adlar istemediğinizi varsayarsak.
jnm2

1

Replace Function ve FOR JSON PATH kullanma

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

Örnek veriler ve daha fazla yol için buraya tıklayın


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.