web-dev-qa-db-ja.com

Sql ServerにおけるStuffと 'For Xml Path'のしくみ

表は以下のとおりです。

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

このクエリは正しく機能しています。しかし、それがどのように機能するのか、またはこれを実行するための他の方法や短い方法があるのか​​という説明が必要です。

これを理解するのは非常に混乱しています。

268
Puneet Chawla

これがどのように動作するのかです:

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のリストに結合して、nameを持つ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 |
-----------------------------------

お役に立てれば!

536
FutbolFan

この記事 は、連結された値を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フラグメントを含む単一行が得られます。

最初の列に列の別名を指定していないため、各行はFOR XML PATHの後に大括弧で指定された名前のXML要素でラップされます。たとえば、FOR XML PATH ('X')を持っていたら、次のようなXML文書が得られます。

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

しかし、要素名を指定していないので、値のリストが表示されるだけです。

,aaa,bbb,...

.value('.', 'varchar(max)')は、 "特別な"文字をXMLエンコードせずに、結果のXMLフラグメントから値を取得するだけです。これで、次のような文字列ができました。

',aaa,bbb,...'

STUFF関数は先頭のコンマを削除し、最終結果は次のようになります。

'aaa,bbb,...'

一見するとかなり紛らわしいように見えますが、他のオプションのいくつかと比較して非常にうまく機能する傾向があります。

62
Richard Deeming

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の場合:長さゼロのストリングを指定した場合、折り返し要素は生成されません。

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 |
-----------------------------------
30
Neha Chopra

この正確なシナリオを処理するために、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/ja-jp/library/mt790580.aspx

編集: 私が最初にこれを投稿したとき、私はSQL Server 2016に言及しました。私はそれを誤って覚えていたか、何かが変わったことを思い出しました。また、非常に感銘を受け、最終的な選択肢を得るために私を引き込んだ多段階レビュープロセスを完全には認識していませんでした。

16
Brian Jorden

for xml pathでは、[ for xml path('ENVLOPE') ]のような値を定義すると、それぞれの行にこれらのタグが追加されます。

<ENVLOPE>
</ENVLOPE>
3
vikas
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

ここで上のクエリでは _ stuff _ 関数が生成されたxml文字列(,)から最初のカンマ(,aaa,bbb,ccc,ddd,eee)を削除するために使われていて、それは(aaa,bbb,ccc,ddd,eee)になります。

そしてFOR XML PATH('')は単にカラムデータを(,aaa,bbb,ccc,ddd,eee)文字列に変換しますが、 _ path _ では ''を渡しているのでXMLタグは作成されません。

そして最後に、 _ id _ columnを使ってレコードをグループ化しました。

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

私はデバッグをして、ついに私の「詰め込まれた」クエリをそれに普通の方法で返しました。

単に

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

デバッグしたトリガーからログテーブルに書き込むテーブルの内容を取得します。

1
SlavaTT