web-dev-qa-db-ja.com

SQL ServerのSQL group_concat関数

従業員というテーブルがある場合

EmpID           EmpName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

このフォーマットで必要な結果:

EmpID           EmpName
----------      -------------
1               Mary, John, Sam
2               Alaina, Edward

Q:このレコードは同じEmployeeテーブルにあります。私はUDF、ストアドプロシージャを使用した経験がほとんどないので、クエリでこれを実行する必要があります。これは、UDF、SPを使用せずに可能です。

22
Gurvinder
  1. FOR XML PATH trick および article
  2. CLRユーザー定義集計
  3. sQL Server以前のバージョン2005の場合-一時テーブル

#1の例

DECLARE @t TABLE (EmpId INT, EmpName VARCHAR(100))
INSERT @t VALUES
(1, 'Mary'),(1, 'John'),(1, 'Sam'),(2, 'Alaina'),(2, 'Edward')
SELECT distinct
    EmpId,
    (
        SELECT EmpName+','
        FROM @t t2
        WHERE t2.EmpId = t1.EmpId
        FOR XML PATH('')
    ) Concatenated
FROM @t t1

最後のカンマを取り除く方法-自分で

#2のCLR集約c#コード

using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
using System.IO;

namespace DatabaseAssembly
{
    [Serializable]
    [SqlUserDefinedAggregate(Format.UserDefined,
        IsInvariantToNulls = true,
        IsInvariantToDuplicates = true,
        IsInvariantToOrder = true,
        MaxByteSize = -1)]
    public struct StringJoin : IBinarySerialize
    {
        private Dictionary<string, string> AggregationList
        {
            get
            {
                if (_list == null)
                    _list = new Dictionary<string, string>();
                return _list;
            }
        }
        private Dictionary<string, string> _list;

        public void Init()
        {

        }

        public void Accumulate(SqlString Value)
        {
            if (!Value.IsNull)
                AggregationList[Value.Value.ToLowerInvariant()] = Value.Value;

        }

        public void Merge(StringJoin Group)
        {
            foreach (var key in Group.AggregationList.Keys)
                AggregationList[key] = Group.AggregationList[key];
        }

        public SqlChars Terminate()
        {
            var sb = new StringBuilder();
            foreach (var value in AggregationList.Values)
                sb.Append(value);
            return new SqlChars(sb.ToString());
        }

        #region IBinarySerialize Members

        public void Read(System.IO.BinaryReader r)
        {

            try
            {
                while (true)
                    AggregationList[r.ReadString()] = r.ReadString();
            }
            catch (EndOfStreamException)
            {

            }
        }

        public void Write(System.IO.BinaryWriter w)
        {
            foreach (var key in AggregationList.Keys)
            {
                w.Write(key);
                w.Write(AggregationList[key]);
            }
        }

        #endregion
    }
}
31
Oleg Dok

@OlegDokから選択した答えは、正しい結果を返す可能性があります。しかし、パフォーマンスはひどい場合があります。このテストシナリオはそれを説明します。

一時テーブルの作成:

CREATE table #temp (EmpId INT, EmpName VARCHAR(100))
;WITH N(N)AS 
(SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))M(N)),
tally(N)AS(SELECT ROW_NUMBER()OVER(ORDER BY N.N)FROM N,N a,N b,N c,N d,N e,N f)
INSERT #temp
SELECT EmpId, EmpName FROM (values(1, 'Mary'),(1, 'John'),(1, 'Sam')) x(EmpId, EmpName)
CROSS APPLY 
(SELECT top 2000 N FROM tally) y
UNION ALL
SELECT EmpId, EmpName FROM (values(2, 'Alaina'),(2, 'Edward')) x(EmpId, EmpName)
CROSS APPLY
(SELECT top 2000 N FROM tally) y

これはわずか10.000行です。しかし、同じEmpIdがたくさんあります。

Olegの回答のこのクエリは、私のデータベースで64秒かかりました。

SELECT distinct
    EmpId,
    (
        SELECT EmpName+','
        FROM #temp t2
        WHERE t2.EmpId = t1.EmpId
        FOR XML PATH('')
    ) Concatenated
FROM #temp t1

この状況で行をクリーンアップする方法は、明確ではありません。このデカルト結合を回避するには、このように結合する前にIDの初期数を減らします。

これは、これを処理する正しい方法です。

;WITH CTE as
(
  SELECT distinct EmpId
  FROM #temp
)
SELECT 
    EmpId,
    STUFF((
        SELECT ','+EmpName
        FROM #temp t2
        WHERE t2.EmpId = t1.EmpId
        FOR XML PATH('')
    ), 1,1,'') Concatenated
FROM CTE t1

これには1秒もかかりません

13
t-clausen.dk

MSSQLにはGROUP_CONCAT関数がないと思います。この article は、行の値を連結するさまざまな方法を示しています。

アイテム数が少なく、事前にわかっている場合の値の連結

SELECT CategoryId,
       MAX( CASE seq WHEN 1 THEN ProductName ELSE '' END ) + ', ' +
       MAX( CASE seq WHEN 2 THEN ProductName ELSE '' END ) + ', ' +
       MAX( CASE seq WHEN 3 THEN ProductName ELSE '' END ) + ', ' +
       MAX( CASE seq WHEN 4 THEN ProductName ELSE '' END )
  FROM ( SELECT p1.CategoryId, p1.ProductName,
                ( SELECT COUNT(*) 
                    FROM Northwind.dbo.Products p2
                   WHERE p2.CategoryId = p1.CategoryId
                     AND p2.ProductName <= p1.ProductName )
           FROM Northwind.dbo.Products p1 ) D ( CategoryId, ProductName, seq )
 GROUP BY CategoryId ;

このリンクのその他の方法。

0
John Woo

これは、最初に示した例のソリューションです。

SELECT DISTINCT emp_name,
STUFF(
(SELECT ', ' + RTRIM(proj_id)
FROM project_members AS t1 
WHERE t1.emp_name = t2.emp_name
FOR XML PATH (''))
, 1, 1, '')
FROM project_members t2
0
Moez Guedri