Microsoft SQL Server 2005でgroup_concat MySQL関数をシミュレートしますか?


347

MySQLベースのアプリをMicrosoft SQL Server 2005に移行しようとしています(選択ではありませんが、それは人生です)。

元のアプリでは、ほぼ完全にANSI-SQL準拠のステートメントを使用しましたが、1つの重要な例外があります。MySQLのgroup_concat関数をかなり頻繁に使用しました。

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ソリューションが好きです
ディエゴ

SQLクエリを使用してコンマ区切りリストを作成するにはどうすればよいですか?-その投稿はより広いので、私はそれを正規のものとして選択します
TMS


リストを作成する順序をどのようにして知ることができますか?たとえば、A100 / B391 / X010を表示しますが、リレーショナルデータベースに暗黙の順序がない場合、X010 / A100 / B391または他の組み合わせと同じくらい簡単にできます。
Steve Ford、

回答:


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
この例はうまくいきましたが、別の集計を実行しようとしてもうまくいきませんでした。「相関名 'pre_trimmed'がFROM句で複数回指定されています」というエラーが発生しました。
PhilChuang、2010年

7
'pre_trimmed'はサブクエリの単なるエイリアスです。エイリアスはサブクエリに必要であり、一意である必要があるため、別のサブクエリではエイリアスを一意のものに変更します...
Koen

2
紛らわしい列名としてtable_nameのない例を示すことができますか?
S.Mason 2017年

169

私はパーティーに少し遅れるかもしれませんが、この方法は私にとってはうまくいき、COALESCE方法よりも簡単です。

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

1
これは、値を連結する方法のみを示しています。group_concatは、値をグループごとに連結します。これは、より困難です(およびOPが必要とするもの)。これを行う方法については、SO 15154644に対する承認済みの回答を参照してください。WHERE句は重要な追加です
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

私自身、そのような問題に直面しました、マーク。残念ながら、MSSQLが時代に対応し、GROUP_CONCATを導入するまで、これは、ここで必要なものについて私が思いつくことができた、オーバーヘッドの多いメソッドの中で最も少ないものです。
J Hardiman、

これをありがとう!これが動作しているSQL Fiddleです:sqlfiddle.com
#!

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プロジェクトを見てください。私はあなたが探していることを正確に実行していると思います。

このプロジェクトには、MySQL GROUP_CONCAT関数と同様の機能を集合的に提供する一連のSQLCLRユーザー定義集計関数(SQLCLR UDA)が含まれています。必要な機能に基づいて最高のパフォーマンスを保証する複数の機能があります...


2
@MaxiWheat:多くの人は投票をクリックする前に質問や回答を注意深く読んでいません。それは彼らの間違いのために所有者の投稿に直接影響を与えます。
スティーブラム

よく働く。私が欠けている唯一の機能は、MySQL group_concat()が好きなことができる列でソートする機能です:GROUP_CONCAT(klascode,'(',name,')' ORDER BY klascode ASC SEPARATOR ', ')
Jan

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

あなたが言ったように@マークはそれが私に問題を引き起こしたスペースキャラクターでした。


変数はexecプランに応じてデータフローとして計算されるため、エンジンはこのメソッドの順序を実際には保証しないと思います。これまでのところ、ほとんどの場合動作します。
phil_w

6

Jハーディマンの回答について、どのように:

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サーバーに非常にイライラしていました(まだ午前中です)。この投稿からの回答は本当に役に立ちました。編集:なぜそれが役に立たなかったのですか?それは私のためのトリックをしました
user422190

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.