Microsoft SQL Server 2005에서 group_concat MySQL 기능을 시뮬레이트합니까?


347

MySQL 기반 앱을 Microsoft SQL Server 2005로 마이그레이션하려고합니다 (선택은 아니지만 수명입니다).

원래 앱에서는 거의 전적으로 ANSI-SQL 호환 명령문을 사용했지만 한 가지 중요한 예외는 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 솔루션이 마음 에 듭니다 .
Diego



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

7
'pre_trimmed'는 하위 쿼리의 별칭입니다. 별명은 부속 조회에 필요하며 고유해야하며, 따라서 다른 부속 조회는 고유 한 것으로 변경하십시오.
Koen

2
table_name이없는 예제를 혼란스러운 열 이름으로 표시 할 수 있습니까?
S.Mason

169

나는 파티에 약간 늦을 수도 있지만이 방법은 저에게 효과적이며 COALESCE 방법보다 쉽습니다.

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

1
이것은 값을 연결하는 방법 만 보여줍니다. group_concat은 그룹별로 값을 연결하므로 더 까다 롭고 OP가 요구하는 것으로 보입니다. 이 작업을 수행하는 방법은 SO 15154644에 대한 답변을 참조하십시오. WHERE 절이 추가되었습니다
DJDave

@DJDave 가이 답변 을 언급하고있었습니다 . 유사한 질문에 대한 답변 도 참조하십시오 .
John Cummings

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

7
좋은 속임수-문제는 공백이있는성에 만 해당되며 공백으로 구분 기호를 대체합니다.
Mark Elliot

이런 문제가 생겼습니다, 마크 불행히도 MSSQL이 시대를 맞이하고 GROUP_CONCAT를 도입 할 때까지이 방법은 여기에서 필요한 작업을 수행 할 수있는 가장 많은 오버 헤드 집약적 방법입니다.
J Hardiman

감사합니다! 다음은 작동하는 SQL Fiddle입니다. sqlfiddle.com/#!6/c5d56/3
10

42

SQL Server 2017 에는 새로운 집계 함수가 도입되었습니다

STRING_AGG ( expression, separator).

문자열 표현식의 값을 연결하고 그 사이에 구분자 값을 배치합니다. 문자열 끝에 구분 기호가 추가되지 않습니다.

연결된 요소는 다음을 추가하여 주문할 수 있습니다. WITHIN GROUP (ORDER BY some_expression)

2005-2016 버전의 경우 일반적으로 허용되는 답변에 XML 방법을 사용합니다.

그러나 일부 상황에서는 실패 할 수 있습니다. 예를 들어 연결할 데이터에 CHAR(29)다음 내용이 포함 된 경우

FOR XML은 데이터를 직렬화 할 수 없습니다. XML에 허용되지 않는 문자 (0x001D)가 포함되어 있기 때문입니다.

모든 문자를 처리 할 수있는보다 강력한 방법은 CLR 집계를 사용하는 것입니다. 그러나이 방법을 사용하면 연결된 요소에 순서를 적용하는 것이 더 어렵습니다.

변수에 할당하는 방법은 보장되지 않으며 프로덕션 코드에서는 피해야합니다.


이 푸른 SQL에서 현재로도 주문 가능합니다 azure.microsoft.com/en-us/roadmap/...
Simon_Weaver

34

Github 의 GROUP_CONCAT 프로젝트를 살펴보십시오. 검색하려는 내용을 정확하게 수행한다고 생각합니다.

이 프로젝트에는 MySQL GROUP_CONCAT 함수와 유사한 기능을 제공하는 SQLCLR 사용자 정의 집계 함수 (SQLCLR UDA) 세트가 포함되어 있습니다. 필요한 기능을 기반으로 최상의 성능을 보장하는 여러 기능이 있습니다 ...


2
@ MaxiWheat : 많은 사람들이 투표를 클릭하기 전에 질문을 읽거나 신중하게 대답하지 않습니다. 실수로 인해 소유자 게시물에 직접 영향을 미칩니다.
Steve Lam

잘 작동합니다. 내가 누락 된 유일한 기능은 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);

@ Mark에서 언급했듯이 공간 문제로 인해 문제가 발생했습니다.


변수가 계산 계획에 따라 데이터 흐름으로 계산되기 때문에 엔진 이이 방법으로 어떤 순서도 실제로 보장하지는 않는다고 생각합니다. 그래도 대부분의 시간 동안 작동하는 것 같습니다.
phil_w

6

J Hardiman의 답변에 대해서는 :

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

1
지금 만 볼 수 있습니다 ... 나는 SQL 서버에 여전히 좌절했을 때 (아직도) 의미가 없었습니다. 이 게시물의 답변은 실제로 실제로 도움이되었습니다. 편집 : 왜 btw가 도움이되지 않았습니까? 그것은 나를 위해 속임수
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를 문자열로 연결하기 위해 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.