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 2018

一部の行のIDが異なる場合、このクエリは機能しません。たとえば、 'ddd'と 'eee'にID 2がある場合
KevinVictor '19年

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はLISTAGG、Oracle 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

この記事では、SQLで文字列を連結するさまざまな方法について説明します。これには、連結された値をXMLエンコードしないコードの改良バージョンが含まれます。

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
クエリでのTypeの用途は何ですか。私はそれを定義するために、XMLパスの結果は値に格納されると思います(間違っているかどうかはわかりません)。
Puneet Chawla、2015

8
@PuneetChawla:ディレクティブが使用してデータを返すためにSQLを伝えるタイプ。これがない場合、データはとして返されます。これは、列に特殊文字がある場合にXMLエンコードの問題を回避するために使用されます。TYPExmlnvarchar(max)name
Richard Deeming 2015

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では、値を連結しています。」しかし、なぜ/どのように','指定された列が('')after xmlパスと組み合わされて連結が発生するのかは明らかではありません
barlop

手順4で文字列操作を行うと、この場合は空白( '')である指定された折り返し要素が使用されます。
vCillusion

1
ポイント4と<Name>が消える理由について疑問に思う方のために。これは、名前をコンマで連結した後、列ではなく値だけが存在するため、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 2017

申し訳ありませんが、@ lostmyloginからの編集を上書きするつもりはありませんでした。そのため、実際に修正編集をプッシュしました。
ブライアンジョーデン2017年

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

ここでは上記のクエリでSTUFFの機能は、単に最初のカンマ削除するために使用されて(,)生成された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 different '、' + CAST(T.ID)FROM Table T where T.ID = 1 FOR XML PATH( ''))、1,1、 '')AS名


-3

私は頻繁にwhere句を使用しています

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.