Microsoft SQL Server 2005'te group_concat MySQL işlevini taklit ediyor musunuz?


347

MySQL tabanlı bir uygulamayı Microsoft SQL Server 2005'e geçirmeye çalışıyorum (seçimle değil, ama hayat bu).

Orijinal uygulamada, neredeyse tamamen ANSI-SQL uyumlu ifadeler kullandık, önemli bir istisna dışında - MySQL'in group_concatişlevini oldukça sık kullandık.

group_concat, bu arada, bunu yapar: diyelim ki çalışan isimleri ve projelerinin bir tablosu ...

SELECT empName, projID FROM project_members;

İadeler:

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

... ve group_concat ile elde ettikleriniz:

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

İadeler:

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

Öyleyse bilmek istiyorum: SQL Server'da işlevselliğini taklit eden kullanıcı tanımlı bir işlev yazmak mümkün müdür group_concat?

UDF'leri, saklı yordamları veya bunun gibi herhangi bir şeyi kullanarak neredeyse hiç deneyimim yok, sadece düz SQL, bu yüzden lütfen çok fazla açıklama tarafında hata yapın :)



Bu eski bir soru, ama burada verilen CLR çözümünü seviyorum .
Diego

olası yinelenen nasıl SQL sorgusu kullanarak bir Virgülle Ayrılmış Liste Oluştur mı? - bu yazı daha geniş, bu yüzden bunu kanonik olarak seçerdim
TMS


Listenin hangi sırayla oluşturulması gerektiğini nasıl anlarsınız, örneğin A100 / B391 / X010'u gösterirsiniz, ancak ilişkisel bir veritabanında örtülü bir sipariş olmadığı için X010 / A100 / B391 veya başka herhangi bir kombinasyon olabilir.
Steve Ford

Yanıtlar:


174

Bunu yapmak için GERÇEK kolay yolu yok. Yine de bir sürü fikir var.

Bulduğum en iyisi :

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;

Veya veriler gibi karakterler içeriyorsa düzgün çalışan bir sürüm <

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
Bu örnek benim için çalıştı, ama başka bir toplama yapmayı denedim ve işe yaramadı, bana bir hata verdi: "bir prefrom yan tümcesinde 'pre_trimmed' korelasyon adı birden çok kez belirtildi."
PhilChuang

7
'pre_trimmed' sadece alt sorgu için bir takma addır. Alt sorgular için takma adlar gereklidir ve benzersiz olmalıdır, bu nedenle başka bir alt sorgu için benzersiz bir şeyle değiştirin ...
Koen

2
tablo_adı olmayan bir örneği kafa karıştırıcı bir sütun adı olarak gösterebilir misiniz?
S.Mason

169

Partiye biraz geç kalmış olabilirim ama bu yöntem benim için çalışıyor ve COALESCE yönteminden daha kolay.

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

1
Bu sadece değerlerin nasıl birleştirileceğini gösterir - group_concat onları daha zorlayıcı olan (ve OP'nin neye ihtiyaç duyduğu gibi) gruba göre sonuçlandırır. Bunun nasıl yapılacağı için SO 15154644'ün kabul edilen cevabına bakın - WHERE cümlesi kritik ektir
DJDave

@DJDave bu cevaba atıfta bulundu . Benzer bir soru için kabul edilen cevaba da bakınız .
John Cummings

51

Şimdi faydalanmak için çok geç, ama bu bir şeyler yapmanın en kolay yolu değil mi?

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

İlginç. Projeyi zaten bitirdim, ancak bu yöntemi bir deneyeceğim. Teşekkürler!
DanM

7
Güzel hüner - tek sorun, boşlukları olan soyadı için alanı ayırıcıyla değiştirecektir.
Mark Elliot

Ben böyle bir problemle karşılaştım Mark. Ne yazık ki, MSSQL zamana ulaşıp GROUP_CONCAT'ı tanıtana kadar, bu, burada ihtiyaç duyulan şey için ortaya koyabildiğim genel yoğun yöntemlerden en azıdır.
J Hardiman

Bunun için teşekkürler! İşte SQL Fiddle o çalışma gösteriyor: sqlfiddle.com/#!6/c5d56/3
fleed

42

SQL Server 2017 yeni bir toplama işlevi sunuyor

STRING_AGG ( expression, separator).

Dize ifadelerinin değerlerini birleştirir ve aralarına ayırıcı değerleri yerleştirir. Dizgenin sonuna ayırıcı eklenmez.

Birleştirilmiş öğeler eklenerek sipariş edilebilir WITHIN GROUP (ORDER BY some_expression)

2005-2016 sürümleri için genellikle kabul edilen yanıtta XML yöntemini kullanırım.

Ancak bu bazı durumlarda başarısız olabilir. örneğin veri Bitiştirilecek eğer içeriyor CHAR(29)Gördüğünüz

FOR XML verileri serileştiremedi ... çünkü XML'de izin verilmeyen bir karakter (0x001D) içeriyor.

Tüm karakterlerle başa çıkabilen daha sağlam bir yöntem bir CLR toplamı kullanmak olacaktır. Bununla birlikte, bu yaklaşımla birleştirilmiş öğelere bir sipariş uygulamak daha zordur.

Değişkene atama yöntemi garanti edilmez ve üretim kodunda bundan kaçınılmalıdır.


Bu Azure SQL şimdi de mevcuttur: azure.microsoft.com/en-us/roadmap/...
Simon_Weaver

34

Github'daki GROUP_CONCAT projesine bir göz atın, tam olarak aradığınızı yaptığımı düşünüyorum:

Bu proje, toplu olarak MySQL GROUP_CONCAT işlevine benzer işlevler sunan bir dizi SQLCLR Kullanıcı Tanımlı Toplama işlevi (SQLCLR UDA) içerir. Gereken işlevselliğe göre en iyi performansı sağlamak için birden fazla işlev vardır ...


2
@MaxiWheat: Birçok kişi oyu tıklamadan önce soru veya cevapları dikkatlice okumuyor. Bu hata nedeniyle doğrudan sahibi sonrası etkiler.
Steve Lam

Harika çalışıyor. Eksik olduğum tek özellik MySQL group_concat () gibi bir sütun üzerinde sıralama yeteneğidir:GROUP_CONCAT(klascode,'(',name,')' ORDER BY klascode ASC SEPARATOR ', ')
Ocak

10

Birden çok proje yöneticisi olan projelerin tüm proje yöneticisi adlarını birleştirmek için:

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

Aşağıdaki kodla, dağıtmadan önce proje özelliklerinizde PermissionLevel = External ayarlamanız ve "ALTER DATABASE database_name SET" komutunu çalıştırarak veritabanını harici koda güvenecek şekilde değiştirmelisiniz (güvenlik riskleri ve alternatifleri [sertifikalar gibi) hakkında başka bir yerde mutlaka okuyun) GÜVENİLİR AÇIK ".

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);
 }
    }

Şuna benzeyen bir sorgu kullanarak test ettim:

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 

Ve verim: A, B, C, D


9

Bunları denedim ama MS SQL Server 2005'teki amacım için xaprb'de bulduğum en yararlı olanı

declare @result varchar(8000);

set @result = '';

select @result = @result + name + ' '

from master.dbo.systypes;

select rtrim(@result);

@ Belirttiğiniz gibi, benim için sorunlara neden olan boşluk karakteriydi.


Motorun bu yöntemle herhangi bir siparişi garanti etmediğini düşünüyorum, çünkü değişkenler exec planına bağlı olarak veri akışı olarak hesaplanır. Yine de çoğu zaman işe yarıyor gibi görünüyor.
phil_w

6

J Hardiman'ın cevabı hakkında ne dersin?

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

Bu arada, "Soyadı" kullanımı bir yazım hatası mı yoksa burada bir kavram mı anlamıyorum?

Her neyse, çok teşekkürler çocuklar çünkü biraz zaman kazandım :)


1
Bana sorarsanız oldukça düşmanca cevap ve bir cevap olarak hiç yararlı değil.
Tim Meers

1
sadece şimdi görüyorum ... Ben ortalama bir şekilde demek istemedim, o zaman ben sql sunucusu ile çok sinirli (hala am). bu yazının cevapları gerçekten yardımcı oldu; EDIT: neden yararlı btw değildi? benim için hile yaptı
user422190

1

Dışarıdaki Google çalışanlarım için, bir süre daha karmaşık çözümlerle mücadele ettikten sonra benim için çalışan çok basit bir tak ve kullan çözümü:

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

Bir dize olarak bitiştirmek için kimliği bir VARCHAR dönüştürmek zorunda olduğuna dikkat edin. Bunu yapmak zorunda değilseniz, işte daha da basit bir sürüm:

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

Bunun için tüm krediler buraya gider: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of-mysql-in- sql-server? forumu = transactsql

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.