Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

SQL Server v.Berikutnya :Performa STRING_AGG()

Sementara SQL Server di Linux telah mencuri hampir semua berita utama tentang v.Selanjutnya, ada beberapa kemajuan menarik lainnya yang datang dalam versi berikutnya dari platform database favorit kami. Di bagian depan T-SQL, kami akhirnya memiliki cara bawaan untuk melakukan penggabungan string yang dikelompokkan:STRING_AGG() .

Katakanlah kita memiliki struktur tabel sederhana berikut:

CREATE TABLE dbo.Objects( [object_id] int, [object_name] nvarchar(261), CONSTRAINT PK_Objects PRIMARY KEY([object_id])); CREATE TABLE dbo.Columns( [object_id] int NOT NULL FOREIGN KEY REFERENCES dbo.Objects([object_id]), nama_kolom sysname, CONSTRAINT PK_Columns PRIMARY KEY ([object_id],column_name));

Untuk pengujian kinerja, kita akan mengisi ini menggunakan sys.all_objects dan sys.all_columns . Tapi untuk demonstrasi sederhana dulu, mari kita tambahkan baris berikut:

INSERT dbo.Objects([object_id],[object_name]) VALUES(1,N'Karyawan'),(2,N'Orders'); INSERT dbo.Columns([object_id],column_name) NILAI(1,N'EmployeeID'),(1,N'CurrentStatus'), (2,N'OrderID'),(2,N'OrderDate'),(2 ,N'IDPelanggan');

Jika forum merupakan indikasi, itu adalah persyaratan yang sangat umum untuk mengembalikan baris untuk setiap objek, bersama dengan daftar nama kolom yang dipisahkan koma. (Ekstrapolasikan bahwa untuk jenis entitas apa pun yang Anda modelkan dengan cara ini – nama produk yang terkait dengan pesanan, nama bagian yang terlibat dalam perakitan produk, bawahan yang melapor ke manajer, dll.) Jadi, misalnya, dengan data di atas kami akan ingin keluaran seperti ini:

kolom objek--------- ----------------------------Employees EmployeeID,CurrentStatusOrders OrderID,OrderDate, ID Pelanggan

Cara kita melakukannya dalam versi SQL Server saat ini mungkin menggunakan FOR XML PATH , seperti yang saya tunjukkan sebagai yang paling efisien di luar CLR di posting sebelumnya ini. Dalam contoh ini, akan terlihat seperti ini:

SELECT [object] =o.[object_name], [columns] =STUFF( (SELECT N',' + c.column_name FROM dbo.Columns AS c WHERE c.[object_id] =o.[object_id] UNTUK XML PATH, TYPE ).nilai(N'.[1]',N'nvarchar(max)'),1,1,N'')FROM dbo.Objects AS o;

Bisa ditebak, kita mendapatkan output yang sama seperti yang ditunjukkan di atas. Dalam SQL Server v.Next, kita akan dapat mengungkapkan ini dengan lebih sederhana:

SELECT [object] =o.[object_name], [columns] =STRING_AGG(c.column_name, N',')FROM dbo.Objects AS oINNER JOIN dbo.Columns AS cON o.[object_id] =c.[ object_id]GROUP BY o.[object_name];

Sekali lagi, ini menghasilkan output yang sama persis. Dan kami dapat melakukan ini dengan fungsi asli, menghindari FOR XML PATH yang mahal perancah, dan STUFF() fungsi yang digunakan untuk menghapus koma pertama (ini terjadi secara otomatis).

Bagaimana dengan Pesanan?

Salah satu masalah dengan banyak solusi kludge untuk penggabungan yang dikelompokkan adalah bahwa urutan daftar yang dipisahkan koma harus dianggap arbitrer dan non-deterministik.

Untuk XML PATH solusi, saya menunjukkan di posting lain sebelumnya bahwa menambahkan ORDER BY sepele dan terjamin. Jadi dalam contoh ini, kita dapat mengurutkan daftar kolom menurut nama kolom menurut abjad alih-alih menyerahkannya ke SQL Server untuk diurutkan (atau tidak):

SELECT [object] =[object_name], [columns] =STUFF( (SELECT N',' +c.column_name FROM dbo.Columns AS c WHERE c.[object_id] =o.[object_id] ORDER BY c. column_name -- hanya ubah FOR XML PATH, TYPE ).value(N'.[1]',N'nvarchar(max)'),1,1,N'')FROM dbo.Objects AS o;

Keluaran:

kolom objek--------- ----------------------------Status Saat Ini Karyawan,EmployeeIDOrder CustomerID,OrderDate, IDPesanan

CTP 1.1 menambahkan WITHIN GROUP ke STRING_AGG() , jadi dengan menggunakan pendekatan baru, kita dapat mengatakan:

SELECT [object] =o.[object_name], [columns] =STRING_AGG(c.column_name, N',') WITHIN GROUP (ORDER BY c.column_name) -- hanya changeFROM dbo.Objects AS oINNER JOIN dbo. Kolom SEBAGAI con o.[object_id] =c.[object_id]GROUP BY o.[object_name];

Sekarang kita mendapatkan hasil yang sama. Perhatikan bahwa, seperti ORDER BY biasa klausa, Anda dapat menambahkan beberapa kolom atau ekspresi pemesanan di dalam WITHIN GROUP () .

Baiklah, Performa Sudah!

Menggunakan prosesor quad-core 2,6 GHz, memori 8 GB, dan SQL Server CTP1.1 (14.0.100.187), saya membuat database baru, membuat ulang tabel ini, dan menambahkan baris dari sys.all_objects dan sys.all_columns . Saya memastikan untuk hanya menyertakan objek yang memiliki setidaknya satu kolom:

INSERT dbo.Objects([object_id], [object_name]) -- 656 baris PILIH [object_id], QUOTENAME(s.name) + N'.' + QUOTENAME(o.name) FROM sys.all_objects AS o INNER JOIN sys.schemas AS s ON o.[schema_id] =s.[schema_id] WHERE EXISTS ( SELECT 1 FROM sys.all_columns WHERE [object_id] =o.[object_id] ] ); INSERT dbo.Columns([object_id], column_name) -- 8.085 baris SELECT [object_id], nama FROM sys.all_columns AS c WHERE EXISTS ( SELECT 1 FROM dbo.Objects WHERE [object_id] =c.[object_id] ); 

Di sistem saya, ini menghasilkan 656 objek dan 8.085 kolom (sistem Anda mungkin menghasilkan angka yang sedikit berbeda).

Rencana

Pertama, mari kita bandingkan paket dan tab Tabel I/O untuk dua kueri tidak berurutan, menggunakan Plan Explorer. Berikut adalah metrik waktu proses keseluruhan:

Metrik waktu proses untuk XML PATH (atas) dan STRING_AGG() (bawah)

Rencana grafis dan Tabel I/O dari FOR XML PATH permintaan:


Rencana dan Tabel I/O untuk XML PATH, tidak ada pesanan

Dan dari STRING_AGG versi:


Rencana dan Tabel I/O untuk STRING_AGG, tidak ada pemesanan

Untuk yang terakhir, pencarian indeks berkerumun tampaknya sedikit mengganggu saya. Ini sepertinya kasus yang bagus untuk menguji FORCESCAN yang jarang digunakan petunjuk (dan tidak, ini pasti tidak akan membantu FOR XML PATH permintaan):

SELECT [object] =o.[object_name], [columns] =STRING_AGG(c.column_name, N',')FROM dbo.Objects AS oINNER JOIN dbo.Columns AS c WITH (FORCESCAN) -- menambahkan petunjuk o .[object_id] =c.[object_id]GROUP BY o.[object_name];

Sekarang tab rencana dan Tabel I/O terlihat banyak lebih baik, setidaknya pada pandangan pertama:


Rencana dan Tabel I/O untuk STRING_AGG(), tanpa pemesanan, dengan FORCESCAN

Versi kueri yang dipesan menghasilkan rencana yang kira-kira sama. Untuk FOR XML PATH versi, semacam ditambahkan:

Menambahkan pengurutan dalam versi UNTUK XML PATH

Untuk STRING_AGG() , pemindaian dipilih dalam kasus ini, bahkan tanpa FORCESCAN petunjuk, dan tidak diperlukan operasi pengurutan tambahan – sehingga rencana terlihat identik dengan FORCESCAN versi.

Pada Skala

Melihat rencana dan metrik runtime satu kali dapat memberi kita gambaran tentang apakah STRING_AGG() berkinerja lebih baik daripada FOR XML PATH yang ada solusi, tetapi tes yang lebih besar mungkin lebih masuk akal. Apa yang terjadi ketika kita melakukan penggabungan yang dikelompokkan 5.000 kali?

SELECT SYSDATETIME();GO DECLARE @x nvarchar(max);SELECT @x =STRING_AGG(c.column_name, N',') FROM dbo.Objects AS o INNER JOIN dbo.Columns AS c ON o.[object_id ] =c.[object_id] GROUP BY o.[object_name];GO 5000SELECT [string_agg, unordered] =SYSDATETIME();GO DECLARE @x nvarchar(max);SELECT @x =STRING_AGG(c.column_name, N',' ) DARI dbo.Objects SEBAGAI o INNER JOIN dbo.Columns AS c WITH (FORCESCAN) ON o.[object_id] =c.[object_id] GROUP BY o.[object_name];GO 5000SELECT [string_agg, unordered, forcecan] =SYSDATETIME( ); GODECLARE @x nvarchar(max);SELECT @x =STUFF((SELECT N',' +c.column_name FROM dbo.Columns AS c WHERE c.[object_id] =o.[object_id] FOR XML PATH, TYPE).nilai (N'.[1]',N'nvarchar(max)'),1,1,N'')FROM dbo.Objects AS o;GO 5000SELECT [untuk jalur xml, tidak berurutan] =SYSDATETIME(); GODECLARE @x nvarchar(max);SELECT @x =STRING_AGG(c.column_name, N',') WITHIN GROUP (ORDER BY c.column_name) FROM dbo.Objects AS o INNER JOIN dbo.Columns AS c ON o.[object_id ] =c.[object_id] KELOMPOK OLEH o.[object_name];PERGI 5000SELECT [string_agg, dipesan] =SYSDATETIME(); GODECLARE @x nvarchar(max);SELECT @x =STUFF((SELECT N',' +c.column_name FROM dbo.Columns AS c WHERE c.[object_id] =o.[object_id] ORDER BY c.column_name FOR XML PATH , TYPE).nilai(N'.[1]',N'nvarchar(max)'),1,1,N'')FROM dbo.Objects AS oORDER BY o.[object_name];GO 5000SELECT [untuk jalur xml , dipesan] =SYSDATETIME();

Setelah menjalankan script ini sebanyak lima kali, saya rata-ratakan angka durasinya dan inilah hasilnya:

Durasi (milidetik) untuk berbagai pendekatan penggabungan yang dikelompokkan

Kita dapat melihat bahwa FORCESCAN petunjuk benar-benar memperburuk keadaan – sementara kami mengalihkan biaya dari pencarian indeks berkerumun, jenisnya sebenarnya jauh lebih buruk, meskipun perkiraan biaya menganggapnya relatif setara. Lebih penting lagi, kita dapat melihat bahwa STRING_AGG() memang menawarkan manfaat kinerja, apakah string yang digabungkan perlu dipesan dengan cara tertentu atau tidak. Seperti STRING_SPLIT() , yang saya lihat kembali pada bulan Maret, saya cukup terkesan bahwa fungsi ini diskalakan dengan baik sebelum "v1."

Saya memiliki tes lebih lanjut yang direncanakan, mungkin untuk posting mendatang:

  • Ketika semua data berasal dari satu tabel, dengan dan tanpa indeks yang mendukung pengurutan
  • Tes kinerja serupa di Linux

Sementara itu, jika Anda memiliki kasus penggunaan khusus untuk penggabungan yang dikelompokkan, silakan bagikan di bawah ini (atau kirim email kepada saya di [email protected]). Saya selalu terbuka untuk memastikan tes saya senyata mungkin.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara membuat banyak satu ke satu

  2. membandingkan kolom dengan daftar nilai di t-sql

  3. Sistem Manajemen Database Paling Populer di Dunia

  4. Cara Menambahkan Pemisah ke String Gabungan di SQL Server – CONCAT_WS()

  5. SQL Server:Sisi gelap NVARCHAR