表は以下のとおりです。
+----+------+
| 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. 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
のパラメータは次のとおりです。
だから私たちはに終わる:
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 |
-----------------------------------
お役に立てれば!
この記事 は、連結された値を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,...'
一見するとかなり紛らわしいように見えますが、他のオプションのいくつかと比較して非常にうまく機能する傾向があります。
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 |
-----------------------------------
この正確なシナリオを処理するために、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に言及しました。私はそれを誤って覚えていたか、何かが変わったことを思い出しました。また、非常に感銘を受け、最終的な選択肢を得るために私を引き込んだ多段階レビュープロセスを完全には認識していませんでした。
for xml path
では、[ for xml path('ENVLOPE') ]
のような値を定義すると、それぞれの行にこれらのタグが追加されます。
<ENVLOPE>
</ENVLOPE>
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
私はデバッグをして、ついに私の「詰め込まれた」クエリをそれに普通の方法で返しました。
単に
select * from myTable for xml path('myTable')
デバッグしたトリガーからログテーブルに書き込むテーブルの内容を取得します。