GROUP BY를 사용하여 SQL Server에서 문자열을 연결하는 방법은 무엇입니까?


373

어떻게 내가 가질까:

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

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

18
이러한 유형의 문제는 GROUP_CONCAT()집계 기능을 사용하여 MySQL에서 쉽게 해결되지만 Microsoft SQL Server에서 해결하는 것이 더 어색합니다. 지원이 필요하면 다음 SO 질문을 참조하십시오 : " 관계를 기반으로 한 레코드에 대해 여러 레코드를 얻을 수 있습니까? "
빌 Karwin

1
마이크로 소프트 계정으로 모두가 연결에 대한 간단한 해결책을 위해 투표를해야합니다 connect.microsoft.com/SQLServer/feedback/details/427987/...
옌스 Mühlenhoff에게

1
: 당신은 SQLCLR 집계가 T-SQL이 개선 될 때까지 대신 여기에서 찾을 수 있습니다 groupconcat.codeplex.com
올랜도 Colamatteo

답변:


550

CURSOR, WHILE 루프 또는 사용자 정의 함수가 필요하지 않습니다 .

FOR XML과 PATH로 창의력을 발휘해야합니다.

[참고 :이 솔루션은 SQL 2005 이상에서만 작동합니다. 원래 질문은 사용중인 버전을 지정하지 않았습니다.]

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
왜 임시 테이블을 잠금 해제합니까?
Amy B

3
이것은 내 인생에서 본 가장 멋진 SQL입니다. 대용량 데이터 세트에 "빠른"아이디어가 있습니까? 커서처럼 크롤링을 시작하지 않습니다. 그렇지 않습니까? 더 많은 사람들이이 미친 듯이 투표하기를 바랍니다.
user12861

6
뭐라고. 난 그냥 그것의 하위 쿼리 스타일을 싫어. JOINS가 훨씬 좋습니다. 이 솔루션에서 사용할 수 있다고 생각하지 마십시오. 어쨌든, 나는 이런 것을 배우는 것을 좋아하는 저 외에 다른 SQL dork이 있다는 것을 알게되어 기쁩니다. 당신에게 모두 :)
케빈 페어차일드

6
문자열 조작을 수행하는 약간 더 깔끔한 방법 : STUFF ((SELECT ','+ [Name] + ':'+ CAST ([Value] AS VARCHAR (MAX))) FROM #YourTable WHERE (ID = Results.ID) FOR XML PATH ( '')), 1,2, '') AS 이름 값
Jonathan Sayce

3
내가 찾은 것을 주목하십시오. 대소 문자를 구분하지 않는 환경에서도 쿼리의 .value 부분은 소문자 여야합니다. 필자는 이것이 XML이기 때문에 대소 문자를 구분
한다고 생각

136

SQL Server 2017 또는 SQL Server Vnext, SQL Azure 인 경우 아래와 같이 string_agg를 사용할 수 있습니다.

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

완벽하게 작동합니다!
argoo

1
이것은 받아 들인 대답보다 훌륭하고 효과적입니다.
Jannick Breunis

51

XML 경로를 사용하면 예상대로 완벽하게 연결되지 않습니다. "&"를 "& amp;"로 바꿉니다. 또한 <" and "> 몇 가지 다른 것들을 망칠 것입니다 .

이에 대한 해결 방법을 발견했습니다 ... 교체해야합니다.

FOR XML PATH('')
)

와:

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

... 또는 NVARCHAR(MAX)그것이 당신이 사용하는 것이라면.

도대체 SQL에 결합 집계 함수 가없는 이유는 무엇입니까? 이것은 PITA입니다.


2
출력을 인코딩하지 않는 가장 좋은 방법을 찾기 위해 그물을 닦았습니다. 정말 고맙습니다! 이것이 바로 정답입니다. MS가 CONCAT () 집계 함수와 같이 이에 대한 적절한 지원을 추가 할 때까지입니다. 내가하는 일은 이것을 연결된 외부 필드로 반환하는 외부 응용 프로그램에 넣는 것입니다. 내 선택 문에 중첩 선택을 추가하는 팬이 아닙니다.
MikeTeeVee 2016 년

Value를 사용하지 않고 텍스트가 XML 인코딩 문자 인 경우 문제가 발생할 수 있다는 데 동의했습니다. SQL Server에서 그룹화 된 연결 시나리오를 다루는 블로그를 찾으십시오. blog.vcillusion.co.in/…
vCillusion

40

내가 문자열에 공백 및 특수 XML 문자 (포함하여 작업에 케빈 페어차일드의 제안을 변환하려고 할 때 나는 몇 가지 문제로 실행 &, <, >인코딩 된).

내 코드의 최종 버전 (원래 질문에 대답하지는 않지만 누군가에게 유용 할 수 있음)은 다음과 같습니다.

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

공백을 구분 기호로 사용하고 모든 공백을 쉼표로 바꾸는 대신 쉼표와 공백을 각 값 앞에 추가 한 다음 STUFF처음 두 문자를 제거하는 데 사용 합니다.

TYPE 지시문 을 사용하면 XML 인코딩이 자동으로 처리됩니다 .


21

Sql Server 2005 이상을 사용하는 다른 옵션

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

입력 해 주셔서 감사합니다. 저는 항상 SQL Server의 문제를 해결하기 위해 CTE 및 재귀 CTE를 사용하는 것을 선호합니다. 이것은 나를 위해 일하는 위대한 일입니다!
gbdavid

외부 적용이있는 쿼리에서 사용할 수 있습니까?
구멍에 불

14

http://groupconcat.codeplex.com 에서 SQLCLR 집계를 설치하십시오.

그런 다음 요청한 결과를 얻기 위해 다음과 같은 코드를 작성할 수 있습니다.

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;

몇 년 전에 사용한 구문은 모든 "XML 경로"트릭보다 훨씬 깨끗하며 매우 잘 작동합니다. SQL CLR 기능이 옵션 일 때 강력히 권장합니다.
오후

12

SQL Server 2005 이상에서는 연결 등을 포함하여 사용자 지정 집계 함수 를 만들 수 있습니다 . 링크 된 기사의 맨 아래에있는 샘플을 참조하십시오.


4
불행히도 이것은 (?) CLR 어셈블리를 사용해야합니다 .. 다음을 다루는 또 다른 문제입니다 :-/

1
예제는 실제 연결 구현에 CLR을 사용하지만 필수는 아닙니다. 연결 집계 함수가 FOR XML을 사용하도록 할 수 있으므로 최소한 나중에 호출해야합니다.
Shiv

12

8 년 후 ... Microsoft SQL Server vNext 데이터베이스 엔진은 그룹화 된 문자열 연결을 직접 지원하도록 Transact-SQL을 향상 시켰습니다. Community Technical Preview 버전 1.0은 STRING_AGG 함수를 추가했으며 CTP 1.1은 STRING_AGG 함수에 WITHIN GROUP 절을 추가했습니다.

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


9

이것은 Kevin Fairchild의 게시물에 추가 된 것입니다 (매우 영리합니다). 나는 그것을 의견으로 추가했을 것이지만, 아직 충분한 지적이 없다. :)

나는이 아이디어를 내가 일하고있는 견해에 사용했지만 연결하는 항목에는 공백이 포함되었습니다. 그래서 공백을 구분 기호로 사용하지 않도록 코드를 약간 수정했습니다.

멋진 해결 방법 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

예를 들면

Oracle에서는 LISTAGG 집계 함수를 사용할 수 있습니다.

원본 기록

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

SQL

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

의 결과

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

6
멋지지만 질문은 특히 Oracle에 관한 것이 아닙니다.
user12861

13
이해 했어요. 그러나 나는 Oracle에 대해서도 같은 것을 찾고 있었기 때문에 나 같은 다른 사람들을 위해 여기에 넣을 것이라고 생각했다
.

@MichalB. 내부 구문이 누락되지 않았습니까? 예 : 그룹 내 listagg (type, ',') (이름순)?
gregory

@gregory : 내 대답을 편집했습니다. 예전 솔루션이 며칠 전부터 사용되었다고 생각합니다. 제안한 현재 양식은 확실히 작동합니다. 감사합니다.
Michal B.

1
미래의 사람들을 위해-다른 플랫폼과 같은 중요한 차이점에 대한 자신의 답변으로 새로운 질문을 작성할 수 있습니다
Mike M

7

이러한 종류의 질문은 여기에서 매우 자주 요청되며 솔루션은 기본 요구 사항에 크게 의존합니다.

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

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

일반적으로 동적 SQL, 사용자 정의 함수 또는 커서가 없으면 SQL 전용 방법이 없습니다.


2
사실이 아니다. cte : s를 사용하는 cyberkiwi의 솔루션은 공급 업체별 해커가없는 순수한 SQL입니다.
Björn Lindqvist

1
질문과 답변 당시에는 재귀 CTE를 이식성이 뛰어나다 고 생각하지 않았지만 현재 Oracle에서 지원합니다. 가장 좋은 솔루션은 플랫폼에 달려 있습니다. SQL Server의 경우 FOR XML 기술 또는 고객 CLR 집계 일 가능성이 높습니다.
케이드 룰

1
모든 질문에 대한 궁극적 인 답변? stackoverflow.com/search?q= [질문이
무엇이든

7

Cade가 말한 것에 덧붙이 기 위해, 이것은 일반적으로 프론트 엔드 디스플레이 일이므로 처리해야합니다. 파일 내보내기 나 다른 "SQL 전용"솔루션과 같은 경우 SQL로 100 %를 작성하는 것이 더 쉽다는 것을 알고 있지만,이 연결은 대부분 디스플레이 계층에서 처리해야합니다.


11
그룹화는 이제 프론트 엔드 디스플레이입니까? 그룹화 된 결과 집합에서 하나의 열을 연결하기위한 유효한 시나리오가 많이 있습니다.
MGOwen

5

커서가 필요하지 않습니다 ... while 루프는 충분합니다.

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

@marc_s 아마도 PRIMARY KEY가 테이블 변수에 선언되어야한다는 비판이 더 낫습니다.
Amy B

@marc_s 추가 조사에서 IO 측정이없는 성능에 대한 거의 모든 논의와 마찬가지로이 기사는 가짜입니다. LAG에 대해 배웠습니다. 감사합니다.
Amy B

4

매우 간단하게합시다 :

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

이 줄을 바꾸십시오 :

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

당신의 질문으로.


3

교차 적용 답변이 없으며 XML 추출이 필요하지 않습니다. Kevin Fairchild가 작성한 것과 약간 다른 버전이 있습니다. 보다 복잡한 쿼리에서 사용하는 것이 더 빠르고 쉽습니다.

   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
텍스트는 XML 인코딩 된 문자입니다 값을 사용하지 않고, 우리는 문제가 실행할 수 있습니다
vCillusion

2

그룹별로 대부분 하나의 항목을 포함하는 경우 다음과 같은 방식으로 성능을 크게 향상시킬 수 있습니다.

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

목록에서 중복 이름을 원하지 않는다고 가정합니다.
jnm2 2016 년

1

바꾸기 기능 및 FOR JSON PATH 사용

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

샘플 데이터 및 기타 방법은 여기를 클릭하십시오


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.