Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Dasar-dasar Ekspresi Tabel, Bagian 12 – Fungsi Bernilai Tabel Sebaris

Artikel ini adalah bagian kedua belas dalam seri tentang ekspresi tabel bernama. Sejauh ini saya telah membahas tabel turunan dan CTE, yang merupakan ekspresi tabel bernama dengan cakupan pernyataan, dan tampilan, yang dapat digunakan kembali dengan ekspresi tabel bernama. Bulan ini saya memperkenalkan fungsi bernilai tabel sebaris, atau iTVF, dan menjelaskan manfaatnya dibandingkan dengan ekspresi tabel bernama lainnya. Saya juga membandingkannya dengan prosedur tersimpan, terutama berfokus pada perbedaan dalam hal strategi pengoptimalan default, dan merencanakan caching dan perilaku penggunaan kembali. Ada banyak hal yang harus dibahas dalam hal pengoptimalan, jadi saya akan memulai diskusi bulan ini dan melanjutkannya bulan depan.

Dalam contoh saya, saya akan menggunakan database sampel yang disebut TSQLV5. Anda dapat menemukan skrip yang membuat dan mengisinya di sini dan diagram ER-nya di sini.

Apa Itu Fungsi Bernilai Tabel Sebaris?

Dibandingkan dengan ekspresi tabel bernama yang dibahas sebelumnya, iTVF sebagian besar menyerupai tampilan. Seperti tampilan, iTVF dibuat sebagai objek permanen dalam database, dan oleh karena itu dapat digunakan kembali oleh pengguna yang memiliki izin untuk berinteraksi dengannya. Keuntungan utama iTVF dibandingkan dengan tampilan adalah fakta bahwa mereka mendukung parameter input. Jadi, cara termudah untuk mendeskripsikan iTVF adalah sebagai tampilan berparameter, meskipun secara teknis Anda membuatnya dengan pernyataan CREATE FUNCTION dan bukan dengan pernyataan CREATE VIEW.

Penting untuk tidak membingungkan iTVF dengan fungsi bernilai tabel multi-pernyataan (MSTVF). Yang pertama adalah ekspresi tabel bernama inlinable yang didasarkan pada satu kueri yang mirip dengan tampilan dan merupakan fokus artikel ini. Yang terakhir adalah modul program yang mengembalikan variabel tabel sebagai outputnya, dengan aliran multi-pernyataan di tubuhnya yang tujuannya adalah untuk mengisi variabel tabel yang dikembalikan dengan data.

Sintaks

Berikut sintaks T-SQL untuk membuat iTVF:

BUAT [ATAU ALTER] FUNGSI [ . ]

[ () ]

PENGEMBALIAN TABEL

[ DENGAN ]

SEBAGAI

KEMBALI

[; ]

Amati dalam sintaks kemampuan untuk menentukan parameter input.

Tujuan atribut SCHEMABIDNING sama dengan tampilan dan harus dievaluasi berdasarkan pertimbangan serupa. Untuk detailnya, lihat Bagian 10 dalam seri ini.

Contoh

Sebagai contoh untuk iTVF, misalkan Anda perlu membuat ekspresi tabel bernama yang dapat digunakan kembali yang menerima sebagai input ID pelanggan (@custid) dan nomor (@n) dan mengembalikan jumlah pesanan terbaru yang diminta dari tabel Sales.Orders untuk pelanggan masukan.

Anda tidak dapat menerapkan tugas ini dengan tampilan karena tampilan tidak mendukung parameter input. Seperti yang disebutkan, Anda dapat menganggap iTVF sebagai tampilan berparameter, dan karena itu, ini adalah alat yang tepat untuk tugas ini.

Sebelum mengimplementasikan fungsi itu sendiri, berikut kode untuk membuat indeks pendukung pada tabel Sales.Orders:

USE TSQLV5;
GO
 
CREATE INDEX idx_nc_cid_odD_oidD_i_eid
  ON Sales.Orders(custid, orderdate DESC, orderid DESC)
  INCLUDE(empid);

Dan berikut kode untuk membuat fungsi tersebut, bernama Sales.GetTopCustOrders:

CREATE OR ALTER FUNCTION Sales.GetTopCustOrders
  ( @custid AS INT, @n AS BIGINT )
RETURNS TABLE
AS
RETURN
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC;
GO

Sama seperti tabel dan tampilan dasar, saat Anda mencari data, Anda menentukan iTVF dalam klausa FROM dari pernyataan SELECT. Berikut adalah contoh permintaan tiga pesanan terbaru untuk pelanggan 1:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(1, 3);

Saya akan merujuk contoh ini sebagai Query 1. Rencana untuk Query 1 ditunjukkan pada Gambar 1.

Gambar 1:Rencana untuk Kueri 1

Apa Sebaris Tentang iTVF?

Jika Anda bertanya-tanya tentang sumber istilah sebaris dalam fungsi bernilai tabel sebaris, ini ada hubungannya dengan bagaimana mereka dioptimalkan. Konsep inlining berlaku untuk keempat jenis ekspresi tabel bernama yang didukung T-SQL, dan sebagian melibatkan apa yang saya jelaskan di Bagian 4 dalam seri sebagai tidak bersarang/substitusi. Pastikan Anda mengunjungi kembali bagian yang relevan di Bagian 4 jika Anda membutuhkan penyegaran.

Seperti yang dapat Anda lihat pada Gambar 1, berkat fakta bahwa fungsinya telah digariskan, SQL Server mampu membuat rencana optimal yang berinteraksi langsung dengan indeks tabel dasar yang mendasarinya. Dalam kasus kami, rencana melakukan pencarian di indeks pendukung yang Anda buat sebelumnya.

iTVF mengambil konsep inlining selangkah lebih maju dengan menerapkan optimasi penyematan parameter secara default. Paul White menjelaskan optimasi penyematan parameter dalam artikelnya yang sangat bagus, Parameter Sniffing, Embedding, dan RECOMPILE Options. Dengan optimasi penyematan parameter, referensi parameter kueri diganti dengan nilai konstanta literal dari eksekusi saat ini, dan kemudian kode dengan konstanta akan dioptimalkan.

Amati dalam rencana pada Gambar 1 bahwa baik predikat seek dari operator Index Seek dan ekspresi teratas dari operator Top menunjukkan nilai konstanta literal yang disematkan 1 dan 3 dari eksekusi kueri saat ini. Mereka tidak menampilkan parameter @custid dan @n, masing-masing.

Dengan iTVF, pengoptimalan penyematan parameter digunakan secara default. Dengan prosedur tersimpan, kueri berparameter dioptimalkan secara default. Anda perlu menambahkan OPTION(RECOMPILE) ke kueri prosedur tersimpan untuk meminta optimasi penyematan parameter. Detail selengkapnya tentang pengoptimalan iTVF versus prosedur tersimpan, termasuk implikasinya, segera.

Memodifikasi Data Melalui iTVFs

Ingat dari Bagian 11 dalam seri bahwa selama persyaratan tertentu terpenuhi, ekspresi tabel bernama dapat menjadi target pernyataan modifikasi. Kemampuan ini berlaku untuk iTVF yang serupa dengan yang diterapkan pada penayangan. Misalnya, berikut kode yang dapat Anda gunakan untuk menghapus tiga pesanan terbaru dari pelanggan 1 (jangan benar-benar menjalankan ini):

DELETE FROM Sales.GetTopCustOrders(1, 3);

Khususnya di database kami, mencoba menjalankan kode ini akan gagal karena penegakan integritas referensial (pesanan yang terpengaruh kebetulan memiliki baris pesanan terkait di tabel Sales.OrderDetails), tetapi kode tersebut valid dan didukung.

iTVF vs. Stored Procedures

Seperti disebutkan sebelumnya, strategi pengoptimalan kueri default untuk iTVF berbeda dari strategi untuk prosedur tersimpan. Dengan iTVF, defaultnya adalah menggunakan optimasi penyematan parameter. Dengan prosedur tersimpan, defaultnya adalah mengoptimalkan kueri berparameter saat menerapkan parameter sniffing. Untuk mendapatkan penyematan parameter untuk kueri prosedur tersimpan, Anda perlu menambahkan OPTION(RECOMPILE).

Seperti banyak strategi dan teknik pengoptimalan, penyematan parameter memiliki kelebihan dan kekurangan.

Kelebihan utamanya adalah memungkinkan penyederhanaan kueri yang terkadang dapat menghasilkan rencana yang lebih efisien. Beberapa dari penyederhanaan itu benar-benar menarik. Paul mendemonstrasikan ini dengan prosedur tersimpan dalam artikelnya, dan saya akan mendemonstrasikan ini dengan iTVF bulan depan.

Kekurangan utama dari pengoptimalan penyematan parameter adalah Anda tidak mendapatkan cache paket yang efisien dan perilaku penggunaan kembali seperti yang Anda lakukan untuk paket berparameter. Dengan setiap kombinasi nilai parameter yang berbeda, Anda mendapatkan string kueri yang berbeda, dan karenanya kompilasi terpisah yang menghasilkan paket cache yang terpisah. Dengan iTVF dengan input konstan, Anda bisa mendapatkan perilaku penggunaan ulang paket, tetapi hanya jika nilai parameter yang sama diulang. Jelas, kueri prosedur tersimpan dengan OPTION(RECOMPILE) tidak akan menggunakan kembali paket bahkan saat mengulang nilai parameter yang sama, berdasarkan permintaan.

Saya akan mendemonstrasikan tiga kasus:

  1. Paket yang dapat digunakan kembali dengan konstanta yang dihasilkan dari pengoptimalan penyematan parameter default untuk kueri iTVF dengan konstanta
  2. Paket berparameter yang dapat digunakan kembali yang dihasilkan dari pengoptimalan default kueri prosedur tersimpan berparameter
  3. Paket yang tidak dapat digunakan kembali dengan konstanta yang dihasilkan dari pengoptimalan penyematan parameter untuk kueri prosedur tersimpan dengan OPTION(RECOMPILE)

Mari kita mulai dengan kasus #1.

Gunakan kode berikut untuk query iTVF kami dengan @custid =1 dan @n =3:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(1, 3);

Sebagai pengingat, ini akan menjadi eksekusi kedua dari kode yang sama karena Anda sudah mengeksekusinya sekali dengan nilai parameter yang sama sebelumnya, menghasilkan rencana yang ditunjukkan pada Gambar 1.

Gunakan kode berikut untuk menanyakan iTVF dengan @custid =2 dan @n =3 sekali:

SELECT orderid, orderdate, empid
FROM Sales.GetTopCustOrders(2, 3);

Saya akan merujuk kode ini sebagai Query 2. Rencana untuk Query 2 ditunjukkan pada Gambar 2.

Gambar 2:Rencana untuk Kueri 2

Ingatlah bahwa paket pada Gambar 1 untuk Kueri 1 mengacu pada ID pelanggan konstan 1 dalam predikat pencarian, sedangkan paket ini mengacu pada ID pelanggan konstan 2.

Gunakan kode berikut untuk memeriksa statistik eksekusi kueri:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders(%';

Kode ini menghasilkan output berikut:

plan_handle         execution_count text                                           query_plan
------------------- --------------- ---------------------------------------------- ----------------
0x06000B00FD9A1...  1               SELECT ... FROM Sales.GetTopCustOrders(2, 3);  <ShowPlanXML...>
0x06000B00F5C34...  2               SELECT ... FROM Sales.GetTopCustOrders(1, 3);  <ShowPlanXML...>

(2 rows affected)

Ada dua paket terpisah yang dibuat di sini:satu untuk kueri dengan ID pelanggan 1, yang digunakan dua kali, dan satu lagi untuk kueri dengan ID pelanggan 2, yang digunakan sekali. Dengan sejumlah besar kombinasi nilai parameter yang berbeda, Anda akan mendapatkan banyak kompilasi dan paket cache.

Mari kita lanjutkan dengan kasus #2:strategi pengoptimalan default dari kueri prosedur tersimpan berparameter. Gunakan kode berikut untuk merangkum kueri kami dalam prosedur tersimpan yang disebut Sales.GetTopCustOrders2:

CREATE OR ALTER PROC Sales.GetTopCustOrders2
  ( @custid AS INT, @n AS BIGINT )
AS
  SET NOCOUNT ON;
 
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC;
GO

Gunakan kode berikut untuk menjalankan prosedur tersimpan dengan @custid =1 dan @n =3 dua kali:

EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;
EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;

Eksekusi pertama memicu optimasi kueri, menghasilkan rencana parameter yang ditunjukkan pada Gambar 3:

Gambar 3:Rencana Penjualan.Proc GetTopCustOrders2

Amati referensi parameter @custid pada predikat seek dan parameter @n pada ekspresi atas.

Gunakan kode berikut untuk menjalankan prosedur tersimpan dengan @custid =2 dan @n =3 sekali:

EXEC Sales.GetTopCustOrders2 @custid = 2, @n = 3;

Paket parameter yang di-cache yang ditunjukkan pada Gambar 3 digunakan kembali.

Gunakan kode berikut untuk memeriksa statistik eksekusi kueri:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders2%';

Kode ini menghasilkan output berikut:

plan_handle         execution_count text                                            query_plan
------------------- --------------- ----------------------------------------------- ----------------
0x05000B00F1604...  3               ...SELECT TOP (@n)...WHERE custid = @custid...; <ShowPlanXML...>

(1 row affected)

Hanya satu paket berparameter yang dibuat dan di-cache, dan digunakan tiga kali, meskipun nilai ID pelanggan berubah.

Mari kita lanjutkan ke kasus #3. Seperti yang disebutkan, dengan kueri prosedur tersimpan, Anda mungkin mendapatkan optimasi penyematan parameter saat menggunakan OPTION(RECOMPILE). Gunakan kode berikut untuk mengubah kueri prosedur untuk menyertakan opsi ini:

CREATE OR ALTER PROC Sales.GetTopCustOrders2
  ( @custid AS INT, @n AS BIGINT )
AS
  SET NOCOUNT ON;
 
  SELECT TOP (@n) orderid, orderdate, empid
  FROM Sales.Orders
  WHERE custid = @custid
  ORDER BY orderdate DESC, orderid DESC
  OPTION(RECOMPILE);
GO

Jalankan proc dengan @custid =1 dan @n =3 dua kali:

EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;
EXEC Sales.GetTopCustOrders2 @custid = 1, @n = 3;

Anda mendapatkan rencana yang sama yang ditunjukkan sebelumnya pada Gambar 1 dengan konstanta yang disematkan.

Jalankan proc dengan @custid =2 dan @n =3 sekali:

EXEC Sales.GetTopCustOrders2 @custid = 2, @n = 3;

Anda mendapatkan rencana yang sama yang ditunjukkan sebelumnya pada Gambar 2 dengan konstanta yang disematkan.

Periksa statistik eksekusi kueri:

SELECT Q.plan_handle, Q.execution_count, T.text, P.query_plan
FROM sys.dm_exec_query_stats AS Q
  CROSS APPLY sys.dm_exec_sql_text(Q.plan_handle) AS T
  CROSS APPLY sys.dm_exec_query_plan(Q.plan_handle) AS P
WHERE T.text LIKE '%Sales.' + 'GetTopCustOrders2%';

Kode ini menghasilkan output berikut:

plan_handle         execution_count text                                            query_plan
------------------- --------------- ----------------------------------------------- ----------------
0x05000B00F1604...  1               ...SELECT TOP (@n)...WHERE custid = @custid...; <ShowPlanXML...>

(1 row affected)

Hitungan eksekusi menunjukkan 1, hanya mencerminkan eksekusi terakhir. SQL Server men-cache rencana yang terakhir dieksekusi, sehingga dapat menampilkan statistik untuk eksekusi itu, tetapi berdasarkan permintaan, itu tidak menggunakan kembali rencana tersebut. Jika Anda memeriksa rencana yang ditampilkan di bawah atribut query_plan, Anda akan menemukan itu adalah yang dibuat untuk konstanta dalam eksekusi terakhir, yang ditunjukkan sebelumnya pada Gambar 2.

Jika Anda mencari kompilasi yang lebih sedikit, dan perilaku penggunaan kembali cache dan rencana yang efisien, pendekatan pengoptimalan prosedur tersimpan default dari kueri berparameter adalah cara yang harus dilakukan.

Ada keuntungan besar yang dimiliki implementasi berbasis iTVF dibandingkan dengan yang berbasis prosedur tersimpan—bila Anda perlu menerapkan fungsi ke setiap baris dalam tabel, dan meneruskan kolom dari tabel sebagai input. Misalnya, Anda perlu mengembalikan tiga pesanan terbaru untuk setiap pelanggan di tabel Sales.Customers. Tidak ada konstruksi kueri yang memungkinkan Anda untuk menerapkan prosedur tersimpan per baris dalam tabel. Anda dapat menerapkan solusi berulang dengan kursor, tetapi selalu merupakan hari yang baik ketika Anda dapat menghindari kursor. Menggabungkan operator APPLY dengan panggilan iTVF, Anda dapat menyelesaikan tugas dengan baik dan bersih, seperti:

SELECT C.custid, O.orderid, O.orderdate, O.empid
FROM Sales.Customers AS C
  CROSS APPLY Sales.GetTopCustOrders( C.custid, 3 ) AS O;

Kode ini menghasilkan output berikut (disingkat):

custid      orderid     orderdate  empid
----------- ----------- ---------- -----------
1           11011       2019-04-09 3
1           10952       2019-03-16 1
1           10835       2019-01-15 1
2           10926       2019-03-04 4
2           10759       2018-11-28 3
2           10625       2018-08-08 3
...

(263 rows affected)

Pemanggilan fungsi menjadi sebaris, dan referensi ke parameter @custid diganti dengan korelasi C.custid. Ini menghasilkan rencana yang ditunjukkan pada Gambar 4.

Gambar 4:Rencanakan kueri dengan APPLY dan Sales.GetTopCustOrders iTVF

Paket memindai beberapa indeks di tabel Sales.Customers untuk mendapatkan set ID pelanggan dan menerapkan pencarian di indeks pendukung yang Anda buat sebelumnya di Sales.Orders per customer. Hanya ada satu rencana sejak fungsi tersebut dimasukkan ke dalam kueri luar, berubah menjadi gabungan yang berkorelasi, atau lateral. Paket ini sangat efisien, terutama ketika kolom custid di Sales.Orders sangat padat, artinya jika ada sedikit customer ID yang berbeda.

Tentu saja, ada cara lain untuk mengimplementasikan tugas ini, seperti menggunakan CTE dengan fungsi ROW_NUMBER. Solusi seperti itu cenderung bekerja lebih baik daripada yang berbasis APPLY ketika kolom custid di tabel Sales.Orders memiliki kepadatan rendah. Either way, tugas khusus yang saya gunakan dalam contoh saya tidak begitu penting untuk tujuan diskusi kita. Maksud saya adalah menjelaskan berbagai strategi pengoptimalan yang digunakan SQL Server dengan alat yang berbeda.

Setelah selesai, gunakan kode berikut untuk pembersihan:

DROP INDEX IF EXISTS idx_nc_cid_odD_oidD_i_eid ON Sales.Orders;

Ringkasan dan Apa Selanjutnya

Jadi, apa yang telah kita pelajari dari ini?

iTVF adalah ekspresi tabel bernama parameter yang dapat digunakan kembali.

SQL Server menggunakan strategi pengoptimalan penyematan parameter dengan iTVF secara default, dan strategi pengoptimalan kueri berparameter dengan kueri prosedur tersimpan. Menambahkan OPTION(RECOMPILE) ke kueri prosedur tersimpan dapat menghasilkan optimasi penyematan parameter.

Jika Anda ingin mendapatkan lebih sedikit kompilasi dan cache rencana yang efisien dan perilaku penggunaan kembali, rencana kueri prosedur berparameter adalah caranya.

Paket untuk kueri iTVF di-cache dan dapat digunakan kembali, selama nilai parameter yang sama diulang.

Anda dapat dengan mudah menggabungkan penggunaan operator APPLY dan iTVF untuk menerapkan iTVF ke setiap baris dari tabel kiri, meneruskan kolom dari tabel kiri sebagai input ke iTVF.

Seperti yang disebutkan, ada banyak hal yang perlu dibahas tentang pengoptimalan iTVF. Bulan ini saya membandingkan iTVF dan prosedur tersimpan dalam hal strategi pengoptimalan default dan merencanakan caching dan perilaku penggunaan kembali. Bulan depan saya akan menggali lebih dalam penyederhanaan yang dihasilkan dari pengoptimalan penyematan parameter.


No
  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Pembuatan Data Sintetis

  2. Mengaktifkan Otentikasi Dua Faktor untuk ScaleGrid DBaaS

  3. Pola Data Referensi:Dapat Diperluas dan Fleksibel

  4. Hasilkan satu set atau urutan tanpa loop – bagian 3

  5. Contoh Meningkatkan Kinerja Kueri dengan Indeks