SQL Server에서 Stuff 및 'For Xml Path'작동 방식


367

테이블은 :

+----+------+
| Id | Name |
+----+------+    
| 1  | aaa  |
| 1  | bbb  |
| 1  | ccc  |
| 1  | ddd  |
| 1  | eee  |
+----+------+

필요한 출력 :

+----+---------------------+
| Id |        abc          |
+----+---------------------+ 
|  1 | aaa,bbb,ccc,ddd,eee |
+----+---------------------+

질문:

SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

이 쿼리는 제대로 작동합니다. 그러나 나는 그것이 어떻게 작동하는지 또는 이것을 할 수있는 다른 방법이나 짧은 방법이 있는지 설명이 필요합니다.

나는 이것을 이해하기가 매우 혼란스러워지고 있습니다.



1
SqlFiddle 페이지 를 만들어 실제로 실제로 작동하는지 확인했습니다. 그것이 다른 사람들을 돕기를 바랍니다.
Sabuncu

1
^ 아마 ID다른 엔터티의 다른 테이블에서 고유 한 것일 수 있으며이 테이블은 그들에 속한 것을 저장하고 있습니다.
Nick Rolando

일부 행에 다른 ID가 있으면이 쿼리가 작동하지 않습니다. 예를 들어 'ddd'와 'eee'에 ID가 2 인 경우
KevinVictor

10
이 페이지를 매월 방문하여 내가 어디로 잘못 갔는지 볼 시간입니다.
Taylor Ackley

답변:


683

작동 방식은 다음과 같습니다.

1. FOR XML로 XML 요소 문자열 가져 오기

쿼리 끝에 FOR XML PATH를 추가하면 PATH 인수에 포함 된 요소 이름과 함께 쿼리 결과를 XML 요소로 출력 할 수 있습니다. 예를 들어, 다음 명령문을 실행하는 경우 :

SELECT ',' + name 
              FROM temp1
              FOR XML PATH ('')

빈 문자열 (FOR XML PATH ( ''))을 전달하면 다음과 같은 결과가 나타납니다.

,aaa,bbb,ccc,ddd,eee

2. STUFF로 선행 쉼표를 제거하십시오

STUFF 문은 문자 그대로 한 문자열을 다른 문자열로 "붙여서"첫 번째 문자열 내의 문자를 대체하지만 결과 값 목록에서 첫 번째 문자를 제거하는 데 간단히 사용합니다.

SELECT abc = STUFF((
            SELECT ',' + NAME
            FROM temp1
            FOR XML PATH('')
            ), 1, 1, '')
FROM temp1

매개 변수 STUFF는 다음 과 같습니다.

  • "채워질"문자열 (이 경우 앞에 쉼표가있는 전체 이름 목록)
  • 문자 삭제 및 삽입을 시작하는 위치 (1, 빈 문자열에 채워 넣음)
  • 삭제할 문자 수 (1, 선행 쉼표)

그래서 우리는 다음과 같이 끝납니다.

aaa,bbb,ccc,ddd,eee

3. 전체 목록을 얻으려면 id에 가입하십시오.

다음으로 임시 테이블의 id 목록에서 이것을 결합하여 이름이있는 ID 목록을 얻습니다.

SELECT ID,  abc = STUFF(
             (SELECT ',' + name 
              FROM temp1 t1
              WHERE t1.id = t2.id
              FOR XML PATH (''))
             , 1, 1, '') from temp1 t2
group by id;

결과는 다음과 같습니다.

-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

도움이 되었기를 바랍니다!


57
Microsoft의 문서 팀 (있는 경우)을 위해 일해야합니다
Fandango68

55
@ Fandango68, @ FutbolFan-그는 Microsoft의 문서 팀에서 일할 수 없습니다. 그의 설명은 너무 분명하고 직접적입니다. ;-)
Chris

1
@ChrisProsser 동의합니다. Oracle은 LISTAGGOracle 11gR2에 기능 을 도입하여 Microsoft보다 앞서 있습니다. 나는 이것을 대신 사용해야하는 날에는 그 기능을 그리워합니다. techonthenet.com/oracle/functions/listagg.php
FutbolFan

2
안녕하세요. 1 단계에서 수행하는 경우 : SELECT name FROM temp1 FOR XML PATH ( '') ... <name> aaa </ name> <name> bbb </ name> ... etc ...를 얻습니다. 처음에는 이것을 인식하지 못합니다 ... SELECT ''+ name ... etc ...로 변경하면 태그가 제거됩니다.
KevinVictor

1
@ChrisProsser-Sybase ASA는 list수십 년 동안 기능을 해왔습니다. 불행히도 Microsoft는 Sybase의 ASE를 기반으로 한 SQLServer를 대신 사용했으며 작년까지 목록 기능을 사용하지 않았습니다. 동의합니다-마음이 번쩍입니다. 그리고 그들은 그렇게합니다 string_agg. 나는 list꽤 명백 하다고 생각했을 것 입니다.
youcantryreachingme

75

이 기사 에서는 연결된 값을 XML로 인코딩하지 않는 코드의 개선 된 버전을 포함하여 SQL에서 문자열을 연결하는 다양한 방법을 다룹니다.

SELECT ID, abc = STUFF
(
    (
        SELECT ',' + name
        FROM temp1 As T2
        -- You only want to combine rows for a single ID here:
        WHERE T2.ID = T1.ID
        ORDER BY name
        FOR XML PATH (''), TYPE
    ).value('.', 'varchar(max)')
, 1, 1, '')
FROM temp1 As T1
GROUP BY id

무슨 일이 일어나고 있는지 이해하려면 내부 쿼리로 시작하십시오.

SELECT ',' + name
FROM temp1 As T2
WHERE T2.ID = 42 -- Pick a random ID from the table
ORDER BY name
FOR XML PATH (''), TYPE

을 지정하기 때문에 FOR XML모든 행을 나타내는 XML 조각이 포함 된 단일 행을 얻게됩니다.

첫 번째 열에 열 별칭을 지정하지 않았기 때문에 각 행은 이름 뒤에 괄호 안에 지정된 이름을 가진 XML 요소로 래핑됩니다 FOR XML PATH. 예 FOR XML PATH ('X')를 들어을 가진 경우 다음 과 같은 XML 문서가 표시됩니다.

<X>,aaa</X>
<X>,bbb</X>
...

그러나 요소 이름을 지정하지 않았으므로 값 목록 만 얻습니다.

,aaa,bbb,...

.value('.', 'varchar(max)')간단히 "특별한"문자를 XML은 인코딩없이, 결과 XML 조각에서 값을 검색합니다. 이제 다음과 같은 문자열이 있습니다.

',aaa,bbb,...'

STUFF함수는 선행 쉼표를 제거하여 다음과 같은 최종 결과를 제공합니다.

'aaa,bbb,...'

언뜻보기에는 혼란스러워 보이지만 다른 옵션과 비교할 때 성능이 뛰어납니다.


2
쿼리에서 유형을 사용하는 것은 무엇입니까? 나는 그것을 정의하기 위해 그것을 생각한다 .XML 경로의 결과는 가치가 저장 될 것이다.
푸네 Chawla

8
@PuneetChawla는 : 지시어는 SQL이 사용하여 데이터를 반환하도록 지시 유형입니다. 그것이 없으면 데이터는로 반환됩니다 . 열에 특수 문자가있는 경우 XML 인코딩 문제를 피하기 위해 여기에서 사용 됩니다. TYPExmlnvarchar(max)name
Richard Deeming

2
@barlop : SimpleTalk 기사에서 설명 하는 것처럼 TYPEand 를 삭제 .value('.', 'varchar(max)')하면 결과에 XML 인코딩 엔터티가 생길 수 있습니다.
Richard Deeming

1
@RichardDeeming 데이터에 꺾쇠 괄호가 있거나 포함되어 있는지 여부
barlop

1
그러나 요소 이름을 지정하지 않았으므로 값 목록 만 얻습니다 . 이것은 내가 놓친 통찰력입니다. 감사합니다.
아담

44

PATH 모드는 SELECT 쿼리에서 XML을 생성하는 데 사용됩니다

1. SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH;  

Ouput:
<row>
<ID>1</ID>
<Name>aaa</Name>
</row>

<row>
<ID>1</ID>
<Name>bbb</Name>
</row>

<row>
<ID>1</ID>
<Name>ccc</Name>
</row>

<row>
<ID>1</ID>
<Name>ddd</Name>
</row>

<row>
<ID>1</ID>
<Name>eee</Name>
</row>

출력은 결과 행 집합의 각 열 값이 행 요소로 래핑되는 요소 중심 XML입니다. SELECT 절은 컬럼 이름에 대한 별명을 지정하지 않으므로 생성 된 하위 요소 이름은 SELECT 절의 해당 컬럼 이름과 동일합니다.

행 집합의 각 행에 대해 태그가 추가됩니다.

2.
SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH('');

Ouput:
<ID>1</ID>
<Name>aaa</Name>
<ID>1</ID>
<Name>bbb</Name>
<ID>1</ID>
<Name>ccc</Name>
<ID>1</ID>
<Name>ddd</Name>
<ID>1</ID>
<Name>eee</Name>

2 단계 : 길이가 0 인 문자열을 지정하면 줄 바꿈 요소가 생성되지 않습니다.

3. 

    SELECT   

           Name  
    FROM temp1
    FOR XML PATH('');

    Ouput:
    <Name>aaa</Name>
    <Name>bbb</Name>
    <Name>ccc</Name>
    <Name>ddd</Name>
    <Name>eee</Name>

4. SELECT   
        ',' +Name  
FROM temp1
FOR XML PATH('')

Ouput:
,aaa,bbb,ccc,ddd,eee

4 단계에서 값을 연결합니다.

5. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1

Ouput:
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee


6. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1 GROUP by iD

Ouput:
ID  abc
1   ,aaa,bbb,ccc,ddd,eee

6 단계에서 날짜별로 ID를 그룹화합니다.

STUFF (source_string, start, length, add_string) 매개 변수 또는 인수 source_string 수정할 소스 문자열. start 길이 문자를 삭제하고 add_string을 삽입 할 source_string의 위치입니다. length source_string에서 삭제할 문자 수입니다. add_string 시작 위치에서 source_string에 삽입 할 문자 순서.

SELECT ID,
    abc = 
    STUFF (
        (SELECT   
                ',' +Name  
        FROM temp1
        FOR XML PATH('')), 1, 1, ''
    )
FROM temp1 GROUP by iD

Output:
-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

1
"4 단계에서 값을 연결합니다."라고 쓴다. 그러나 xml 경로 이후 ','와 결합 된 열로 지정된 이유 / 어떻게 ('')연결이 발생 하는지는 확실하지 않습니다.
barlop

4 단계에서 문자열 조작을 수행하면이 경우 공백 ( '') 인 지정된 랩핑 요소를 사용합니다.
vCillusion

1
포인트 4와 왜 <이름>이 (가) 사라지는 지 궁금해하는 사람. 쉼표로 이름을 연결 한 후에는 더 이상 열이 아니라 값만 있기 때문에 SQL Server는 어떤 XML 태그 이름을 사용해야하는지 알 수 없기 때문입니다. 예를 들어이 쿼리 SELECT 'a' FROM some_table FOR XML PATH('')는 다음을 생성 'aaaaaaa'합니다.. 그러나 열 이름을 지정 SELECT 'a' AS Col FROM some_table FOR XML PATH('')하면 다음과 같은 결과가 나타납니다.<Col>a</Col><Col>a</Col><Col>a</Col>
anth

23

이 정확한 시나리오를 처리하기 위해 Azure SQL Database 및 SQL Server (2017부터 시작)에 새로운 기능이 있습니다. 나는 이것이 XML / STUFF 방법으로 달성하려는 것에 대한 공식적인 공식 방법이 될 것이라고 생각합니다. 예:

select id, STRING_AGG(name, ',') as abc
from temp1
group by id

STRING_AGG- https: //msdn.microsoft.com/en-us/library/mt790580.aspx

편집 : 처음에 이것을 게시했을 때 SQL Server 2016에 대해 언급했을 때 포함 될 잠재적 기능에 대해 생각했습니다. 수정 된 수정 버전 덕분에 잘못되었거나 변경된 것이 있음을 기억했습니다. 또한 최종 옵션으로 나를 데려 간 다단계 검토 프로세스를 완전히 인식하지 못했습니다.


3
STRING_AGG는 SQL Server 2016에 없습니다. "vNext"에 있다고합니다.
N8allan

죄송합니다. @lostmylogin에서 수정 한 내용을 덮어 쓰려고하지 않았습니다. 죄송합니다. 그게 바로 수정 편집을 진행 한 사람입니다.
Brian Jorden

5

에서 for xml path같은 값을 정의하면 [ for xml path('ENVLOPE') ]이 태그가 각 행에 추가됩니다.

<ENVLOPE>
</ENVLOPE>

2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

다음은 위의 질의에 물건 기능은 첫 번째 쉼표 제거하는 데 사용됩니다 (,)생성 된 XML 문자열에서를 (,aaa,bbb,ccc,ddd,eee)다음이 될 것입니다(aaa,bbb,ccc,ddd,eee) .

그리고 FOR XML PATH('')단순히 열 데이터를 (,aaa,bbb,ccc,ddd,eee)문자열 로 변환 하지만 PATH 에서는 ''을 전달하므로 XML 태그를 만들지 않습니다.

그리고 마지막으로 ID 열을 사용하여 레코드를 그룹화했습니다 .


2

나는 디버깅을하고 마침내 '채워진'쿼리를 정상적인 방법으로 반환했습니다.

간단히

select * from myTable for xml path('myTable')

디버그하는 트리거에서 로그 테이블에 쓸 테이블의 내용을 제공합니다.


1
Declare @Temp As Table (Id Int,Name Varchar(100))
Insert Into @Temp values(1,'A'),(1,'B'),(1,'C'),(2,'D'),(2,'E'),(3,'F'),(3,'G'),(3,'H'),(4,'I'),(5,'J'),(5,'K')
Select X.ID,
stuff((Select ','+ Z.Name from @Temp Z Where X.Id =Z.Id For XML Path('')),1,1,'')
from @Temp X
Group by X.ID

-1

STUFF ((SELECT SELECT ','+ CAST (T.ID) FROM T TIDID = 1 FOR XML PATH ( '')), 1,1, '') AS 이름


-3

with 절을 자주 사용하고 있습니다.

SELECT 
TapuAda=STUFF(( 
SELECT ','+TBL.TapuAda FROM (
SELECT TapuAda FROM T_GayrimenkulDetay AS GD 
INNER JOIN dbo.T_AktiviteGayrimenkul AS AG ON  D.GayrimenkulID=AG.GayrimenkulID WHERE 
AG.AktiviteID=262
) AS TBL FOR XML PATH ('')
),1,1,'')

2
나는 이것이 어떻게 대답인지 알지 못합니다. 몇 가지 설명으로 던질 수 있습니까?
Gar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.