Bagaimana Stuff dan 'For Xml Path' bekerja di Sql Server


367

Meja adalah:

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

Output yang dibutuhkan:

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

Pertanyaan:

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

Kueri ini berfungsi dengan baik. Tetapi saya hanya perlu penjelasan bagaimana cara kerjanya atau apakah ada cara lain atau pendek untuk melakukan ini.

Saya menjadi sangat bingung untuk memahami hal ini.



1
Saya membuat halaman SqlFiddle untuk ini, untuk melihatnya bekerja dalam kehidupan nyata. Semoga ini bisa membantu orang lain.
Sabuncu

1
^ Mungkin IDunik dalam tabel yang berbeda dari entitas yang berbeda, dan tabel ini menyimpan barang-barang milik mereka.
Nick Rolando

Kueri ini tidak berfungsi jika beberapa baris memiliki Id berbeda. mis. jika 'ddd' dan 'eee' memiliki Id 2.
KevinVictor

10
Saatnya kunjungan bulanan saya ke halaman ini untuk melihat kesalahan saya.
Taylor Ackley

Jawaban:


683

Inilah cara kerjanya:

1. Dapatkan string elemen XML dengan UNTUK XML

Menambahkan UNTUK XML PATH ke akhir permintaan memungkinkan Anda untuk output hasil permintaan sebagai elemen XML, dengan nama elemen yang terkandung dalam argumen PATH. Misalnya, jika kita menjalankan pernyataan berikut:

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

Dengan mengirimkan string kosong (UNTUK XML PATH ('')), kami mendapatkan yang berikut:

,aaa,bbb,ccc,ddd,eee

2. Hapus koma terkemuka dengan STUFF

Pernyataan STUFF secara harfiah "memasukkan" satu string ke string lain, menggantikan karakter di dalam string pertama. Namun, kami menggunakannya hanya untuk menghapus karakter pertama dari daftar nilai yang dihasilkan.

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

Parameternya STUFFadalah:

  • String yang akan "diisi" (dalam kasus kami daftar lengkap nama dengan koma terkemuka)
  • Lokasi untuk mulai menghapus dan memasukkan karakter (1, kami memasukkan ke string kosong)
  • Jumlah karakter yang akan dihapus (1, menjadi koma utama)

Jadi kita berakhir dengan:

aaa,bbb,ccc,ddd,eee

3. Bergabunglah pada id untuk mendapatkan daftar lengkap

Selanjutnya kita cukup gabungkan ini pada daftar id di tabel temp, untuk mendapatkan daftar ID dengan nama:

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;

Dan kami memiliki hasil kami:

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

Semoga ini membantu!


57
Anda harus bekerja untuk tim dokumentasi Microsoft (jika ada)
Fandango68

55
@ Fandango68, @ FutbolFan - Dia tidak bisa bekerja untuk tim dokumentasi Microsoft. Penjelasannya terlalu jelas dan terlalu langsung. ;-)
Chris

1
@ ChrisProsser Saya setuju. Oracle telah mengungguli Microsoft dalam hal ini dengan memperkenalkan LISTAGGfungsi dalam Oracle 11gR2. Saya melewatkan fungsi itu pada hari-hari ketika saya harus menggunakan ini sebagai gantinya. techonthenet.com/oracle/functions/listagg.php
FutbolFan

2
Halo. Pada langkah 1, jika Anda melakukannya: SELECT name FROM temp1 FOR XML PATH ('') ... Anda mendapatkan <name>aaa</name> <name> bbb </name> ... dll ... Saya tidak t menyadari ini pada awalnya ... Mengubahnya ke SELECT '' + nama ... dll ... menghapus tag.
KevinVictor

1
@ChrisProsser - Sybase ASA telah memiliki listfungsi selama beberapa dekade. Sayangnya Microsoft berbasis SQLServer di ASE Sybase sebagai gantinya, dan tidak pernah repot dengan fungsi daftar sampai tahun lalu. Saya setuju - ini membingungkan. Dan kemudian mereka melakukannya, mereka menyebutnya string_agg. Saya pikir listcukup jelas.
youcantryreachingme

75

Artikel ini mencakup berbagai cara untuk merangkai string dalam SQL, termasuk versi yang disempurnakan dari kode Anda yang tidak mengkodekan nilai-nilai gabungan 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

Untuk memahami apa yang terjadi, mulailah dengan kueri batin:

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

Karena Anda menentukan FOR XML, Anda akan mendapatkan satu baris berisi fragmen XML yang mewakili semua baris.

Karena Anda belum menentukan alias kolom untuk kolom pertama, setiap baris akan dibungkus dengan elemen XML dengan nama yang ditentukan dalam tanda kurung setelah FOR XML PATH. Misalnya, jika sudah FOR XML PATH ('X'), Anda akan mendapatkan dokumen XML yang tampak seperti:

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

Tapi, karena Anda belum menentukan nama elemen, Anda hanya mendapatkan daftar nilai:

,aaa,bbb,...

The .value('.', 'varchar(max)')hanya mengambil nilai dari fragmen XML yang dihasilkan, tanpa XML-encoding setiap "khusus" karakter. Anda sekarang memiliki string yang terlihat seperti:

',aaa,bbb,...'

The STUFFFungsi kemudian menghapus terkemuka koma, memberikan Anda hasil akhir yang terlihat seperti:

'aaa,bbb,...'

Sekilas terlihat membingungkan, tetapi cenderung berkinerja cukup baik dibandingkan dengan beberapa opsi lain.


2
Apa gunanya Ketik dalam kueri Anda. Saya pikir itu untuk mendefinisikan, hasil jalur XML akan menyimpan nilai (tidak yakin menjelaskannya jika salah).
Puneet Chawla

8
@PuneetChawla: The TYPEdirektif memberitahu SQL untuk mengembalikan data menggunakan xmljenis. Tanpa itu, data dikembalikan sebagai nvarchar(max). Ini digunakan di sini untuk menghindari masalah penyandian XML jika ada karakter khusus di namekolom.
Richard Deeming

2
@barlop: Seperti yang dijelaskan oleh artikel SimpleTalk , jika Anda menjatuhkan TYPEdan .value('.', 'varchar(max)'), maka Anda bisa berakhir dengan entitas yang disandikan dalam hasil.
Richard Deeming

1
@RichardDeeming maksud Anda jika data mengandung atau mungkin mengandung kurung sudut?
barlop

1
Tapi, karena Anda belum menentukan nama elemen, Anda hanya mendapatkan daftar nilai , ini adalah wawasan yang saya lewatkan. Terima kasih.
Adam

44

Mode PATH digunakan dalam menghasilkan XML dari kueri SELECT

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>

Outputnya adalah elemen-sentris XML di mana setiap nilai kolom dalam rowset yang dihasilkan dibungkus dalam elemen baris. Karena klausa SELECT tidak menentukan alias untuk nama kolom, nama elemen anak yang dihasilkan sama dengan nama kolom yang sesuai dalam klausa SELECT.

Untuk setiap baris dalam rowset, sebuah tag ditambahkan.

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>

Untuk Langkah 2: Jika Anda menentukan string panjang nol, elemen pembungkus tidak diproduksi.

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

Pada Langkah 4 kita menggabungkan nilai-nilai.

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

Pada Langkah 6 kami mengelompokkan tanggal dengan ID.

STUFF (source_string, start, length, add_string) Parameter atau Argumen source_string String sumber untuk dimodifikasi. mulai Posisi di source_string untuk menghapus karakter panjang dan kemudian masukkan add_string. length Jumlah karakter yang akan dihapus dari source_string. add_string Urutan karakter untuk dimasukkan ke dalam source_string di posisi awal.

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
Anda menulis "Pada Langkah 4 kita menggabungkan nilai-nilai." Tetapi tidak jelas mengapa / bagaimana kolom yang ','ditentukan, dikombinasikan dengan ('')path setelah xml, menyebabkan penggabungan terjadi
barlop

Pada Langkah 4, melakukan operasi string apa pun akan menggunakan elemen pembungkus yang ditentukan yang kosong ('') untuk kasus ini.
vCillusion

1
Bagi siapa pun yang bertanya tentang poin 4 dan mengapa <Name> menghilang. Itu karena setelah Nama gabungan dengan koma tidak ada lagi kolom tetapi hanya nilai, jadi SQL Server tidak tahu nama untuk tag xml yang harus digunakan. Sebagai contoh query ini SELECT 'a' FROM some_table FOR XML PATH('')akan menghasilkan: 'aaaaaaa'. Tetapi jika nama kolom akan ditentukan: SELECT 'a' AS Col FROM some_table FOR XML PATH('')Anda mendapatkan hasil:<Col>a</Col><Col>a</Col><Col>a</Col>
anth

23

Ada fungsi yang sangat baru di Azure SQL Database dan SQL Server (dimulai dengan 2017) untuk menangani skenario yang tepat ini. Saya percaya ini akan berfungsi sebagai metode resmi asli untuk apa yang Anda coba capai dengan metode XML / STUFF. Contoh:

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

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

EDIT: Ketika saya awalnya memposting ini saya menyebutkan SQL Server 2016 karena saya pikir saya melihat bahwa pada fitur potensial yang harus dimasukkan. Entah saya ingat itu salah atau ada yang berubah, terima kasih atas edit yang disarankan untuk memperbaiki versinya. Juga, cukup terkesan dan tidak sepenuhnya menyadari proses peninjauan multi-langkah yang hanya menarik saya untuk opsi terakhir.


3
STRING_AGG tidak dalam SQL Server 2016. Dikatakan datang dalam "vNext".
N8allan

Ups, saya tidak bermaksud menimpa hasil edit dari @lostmylogin maaf tentang itu ... Itulah yang sebenarnya mendorong melalui koreksi koreksi.
Brian Jorden

5

Di for xml path, jika kita mendefinisikan nilai apa pun [ for xml path('ENVLOPE') ]maka tag ini akan ditambahkan dengan setiap baris:

<ENVLOPE>
</ENVLOPE>

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

Di sini, dalam fungsi STUFF permintaan di atas digunakan untuk hanya menghapus koma pertama (,)dari string xml yang dihasilkan (,aaa,bbb,ccc,ddd,eee)maka akan menjadi (aaa,bbb,ccc,ddd,eee).

Dan FOR XML PATH('')cukup mengonversi data kolom menjadi (,aaa,bbb,ccc,ddd,eee)string tetapi di PATH kita melewati '' sehingga tidak akan membuat tag XML.

Dan pada akhirnya kami telah mengelompokkan catatan menggunakan kolom ID .


2

Saya melakukan debugging dan akhirnya mengembalikan permintaan 'boneka' saya ke sana dengan cara biasa.

Secara sederhana

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

memberi saya isi tabel untuk menulis ke tabel log dari pemicu saya debug.


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 ((PILIH berbeda ',' + CAST (T.ID) DARI Tabel T di mana T.ID = 1 UNTUK XML PATH ('')), 1,1, '') SEBAGAI Nama


-3

Saya sering menggunakan dengan klausa mana

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
Saya tidak melihat bagaimana ini merupakan jawaban, bisakah Anda memberikan beberapa penjelasan?
Gar
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.