Standar ISO/IEC 9075:2016 (SQL:2016) mendefinisikan fitur yang disebut fungsi jendela bersarang. Fitur ini memungkinkan Anda untuk menyarangkan dua jenis fungsi jendela sebagai argumen dari fungsi agregat jendela. Idenya adalah untuk memungkinkan Anda merujuk ke nomor baris, atau nilai ekspresi, pada penanda strategis di elemen windowing. Penanda memberi Anda akses ke baris pertama atau terakhir di partisi, baris pertama atau terakhir dalam bingkai, baris luar saat ini, dan baris bingkai saat ini. Ide ini sangat kuat, memungkinkan Anda untuk menerapkan pemfilteran dan jenis manipulasi lain dalam fungsi jendela Anda yang terkadang sulit untuk dicapai sebaliknya. Anda juga dapat menggunakan fungsi jendela bersarang untuk dengan mudah meniru fitur lain, seperti bingkai berbasis RANGE. Fitur ini saat ini tidak tersedia di T-SQL. Saya memposting saran untuk meningkatkan SQL Server dengan menambahkan dukungan untuk fungsi jendela bersarang. Pastikan untuk menambahkan suara Anda jika Anda merasa fitur ini bermanfaat bagi Anda.
Apa yang bukan tentang fungsi jendela bersarang
Pada tanggal penulisan ini, tidak banyak informasi yang tersedia di luar sana tentang fungsi jendela bersarang standar yang sebenarnya. Yang membuatnya lebih sulit adalah saya belum tahu platform apa pun yang menerapkan fitur ini. Faktanya, menjalankan pencarian web untuk fungsi jendela bersarang mengembalikan sebagian besar cakupan dan diskusi tentang pengelompokan fungsi agregat bersarang dalam fungsi agregat berjendela. Misalnya, Anda ingin membuat kueri tampilan Sales.OrderValues dalam database sampel TSQLV5, dan pengembalian untuk setiap pelanggan dan tanggal pesanan, total nilai pesanan harian, dan total berjalan hingga hari ini. Tugas seperti itu melibatkan pengelompokan dan windowing. Anda mengelompokkan baris menurut ID pelanggan dan tanggal pesanan, dan menerapkan jumlah berjalan di atas jumlah grup nilai pesanan, seperti:
USE TSQLV5; -- http://tsql.solidq.com/SampleDatabases/TSQLV5.zip SELECT custid, orderdate, SUM(val) AS daytotal, SUM(SUM(val)) OVER(PARTITION BY custid ORDER BY orderdate ROWS UNBOUNDED PRECEDING) AS runningsum FROM Sales.OrderValues GROUP BY custid, orderdate;
Kueri ini menghasilkan keluaran berikut, yang ditampilkan di sini dalam bentuk singkatan:
custid orderdate daytotal runningsum ------- ---------- -------- ---------- 1 2018-08-25 814.50 814.50 1 2018-10-03 878.00 1692.50 1 2018-10-13 330.00 2022.50 1 2019-01-15 845.80 2868.30 1 2019-03-16 471.20 3339.50 1 2019-04-09 933.50 4273.00 2 2017-09-18 88.80 88.80 2 2018-08-08 479.75 568.55 2 2018-11-28 320.00 888.55 2 2019-03-04 514.40 1402.95 ...
Meskipun teknik ini cukup keren, dan meskipun pencarian web untuk fungsi jendela bersarang sebagian besar mengembalikan teknik seperti itu, bukan itu yang dimaksud standar SQL dengan fungsi jendela bersarang. Karena saya tidak dapat menemukan informasi apa pun di luar sana tentang topik tersebut, saya hanya perlu mencari tahu dari standar itu sendiri. Semoga artikel ini akan meningkatkan kesadaran akan fitur fungsi jendela bersarang yang sebenarnya, dan menyebabkan orang beralih ke Microsoft dan meminta untuk menambahkan dukungan untuknya di SQL Server.
Apa fungsi jendela bersarang tentang
Fungsi jendela bersarang menyertakan dua fungsi yang dapat Anda susun sebagai argumen dari fungsi agregat jendela. Itu adalah fungsi nomor baris bersarang, dan ekspresi value_of bersarang pada fungsi baris.
Fungsi nomor baris bersarang
Fungsi nomor baris bersarang memungkinkan Anda untuk merujuk ke nomor baris penanda strategis di elemen jendela. Berikut sintaks fungsinya:
Penanda baris yang dapat Anda tentukan adalah:
- BEGIN_PARTITION
- END_PARTITION
- BEGIN_FRAME
- END_FRAME
- CURRENT_ROW
- FRAME_ROW
Empat penanda pertama cukup jelas. Adapun dua yang terakhir, penanda CURRENT_ROW mewakili baris luar saat ini, dan FRAME_ROW mewakili baris bingkai bagian dalam saat ini.
Sebagai contoh untuk menggunakan fungsi nomor baris bersarang, pertimbangkan tugas berikut. Anda perlu mengkueri tampilan Sales.OrderValues, dan mengembalikan untuk setiap pesanan beberapa atributnya, serta perbedaan antara nilai pesanan saat ini dan rata-rata pelanggan, tetapi mengecualikan pesanan pelanggan pertama dan terakhir dari rata-rata.
Tugas ini dapat dicapai tanpa fungsi jendela bersarang, tetapi solusinya melibatkan beberapa langkah:
WITH C1 AS ( SELECT custid, val, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate, orderid ) AS rownumasc, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate DESC, orderid DESC ) AS rownumdesc FROM Sales.OrderValues ), C2 AS ( SELECT custid, AVG(val) AS avgval FROM C1 WHERE 1 NOT IN (rownumasc, rownumdesc) GROUP BY custid ) SELECT O.orderid, O.custid, O.orderdate, O.val, O.val - C2.avgval AS diff FROM Sales.OrderValues AS O LEFT OUTER JOIN C2 ON O.custid = C2.custid;
Berikut adalah output dari kueri ini, yang ditampilkan di sini dalam bentuk singkatan:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10411 10 2018-01-10 966.80 -570.184166 10743 4 2018-11-17 319.20 -809.813636 11075 68 2019-05-06 498.10 -1546.297500 10388 72 2017-12-19 1228.80 -358.864285 10720 61 2018-10-28 550.00 -144.744285 11052 34 2019-04-27 1332.00 -1164.397500 10457 39 2018-02-25 1584.00 -797.999166 10789 23 2018-12-22 3687.00 1567.833334 10434 24 2018-02-03 321.12 -1329.582352 10766 56 2018-12-05 2310.00 1015.105000 ...
Menggunakan fungsi nomor baris bersarang, tugas dapat dicapai dengan satu kueri, seperti:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN ROW_NUMBER(FRAME_ROW) NOT IN ( ROW_NUMBER(BEGIN_PARTITION), ROW_NUMBER(END_PARTITION) ) THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate, orderid ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS diff FROM Sales.OrderValues;
Selain itu, solusi yang saat ini didukung memerlukan setidaknya satu pengurutan dalam rencana, dan beberapa melewati data. Solusi yang menggunakan fungsi nomor baris bersarang memiliki semua potensi untuk dioptimalkan dengan mengandalkan urutan indeks, dan pengurangan jumlah lintasan data. Ini, tentu saja, bergantung pada implementasi.
Ekspresi value_of bersarang pada fungsi baris
Nilai ekspresi_ekspresi bersarang pada fungsi baris memungkinkan Anda untuk berinteraksi dengan nilai ekspresi pada penanda baris strategis yang sama yang disebutkan sebelumnya dalam argumen fungsi agregat jendela. Berikut sintaks dari fungsi ini:
>) LEBIH(
Seperti yang Anda lihat, Anda dapat menentukan delta negatif atau positif tertentu sehubungan dengan penanda baris, dan secara opsional memberikan nilai default jika baris tidak ada pada posisi yang ditentukan.
Kemampuan ini memberi Anda banyak kekuatan saat Anda perlu berinteraksi dengan berbagai titik dalam elemen windowing. Pertimbangkan fakta bahwa sekuat fungsi jendela dapat dibandingkan dengan alat alternatif seperti subkueri, fungsi jendela apa yang tidak didukung adalah konsep dasar korelasi. Menggunakan penanda CURRENT_ROW Anda mendapatkan akses ke baris luar, dan cara ini meniru korelasi. Pada saat yang sama, Anda mendapatkan manfaat dari semua keunggulan yang dimiliki fungsi jendela dibandingkan dengan subkueri.
Sebagai contoh, anggaplah Anda perlu mengkueri tampilan Sales.OrderValues, dan mengembalikan beberapa atributnya untuk setiap pesanan, serta perbedaan antara nilai pesanan saat ini dan rata-rata pelanggan, tetapi mengecualikan pesanan yang dilakukan pada tanggal yang sama dengan tanggal pesanan saat ini. Ini membutuhkan kemampuan yang mirip dengan korelasi. Dengan ekspresi value_of bersarang pada fungsi baris, menggunakan penanda CURRENT_ROW, ini dapat dicapai dengan mudah seperti:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues;
Kueri ini seharusnya menghasilkan keluaran berikut:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10248 85 2017-07-04 440.00 180.000000 10249 79 2017-07-05 1863.40 1280.452000 10250 34 2017-07-08 1552.60 -854.228461 10251 84 2017-07-08 654.06 -293.536666 10252 76 2017-07-09 3597.90 1735.092728 10253 34 2017-07-10 1444.80 -970.320769 10254 14 2017-07-11 556.62 -1127.988571 10255 68 2017-07-12 2490.50 617.913334 10256 88 2017-07-15 517.80 -176.000000 10257 35 2017-07-16 1119.90 -153.562352 ...
Jika Anda berpikir bahwa tugas ini dapat dicapai dengan mudah dengan subkueri yang berkorelasi, dalam kasus sederhana ini Anda benar. Hal yang sama dapat dicapai dengan kueri berikut:
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate ) AS diff FROM Sales.OrderValues AS O1;
Namun, ingat bahwa subquery beroperasi pada tampilan data yang independen, sedangkan fungsi jendela beroperasi pada set yang disediakan sebagai input ke langkah pemrosesan kueri logis yang menangani klausa SELECT. Biasanya, kueri yang mendasarinya memiliki logika tambahan seperti gabungan, filter, pengelompokan, dan semacamnya. Dengan subquery, Anda perlu menyiapkan CTE pendahuluan, atau mengulangi logika kueri yang mendasarinya juga di subquery. Dengan fungsi jendela, tidak perlu mengulang logika apa pun.
Misalnya, Anda seharusnya beroperasi hanya pada pesanan yang dikirim (di mana tanggal pengiriman bukan NULL) yang ditangani oleh karyawan 3. Solusi dengan fungsi jendela perlu menambahkan predikat filter hanya sekali, seperti:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues WHERE empid = 3 AND shippeddate IS NOT NULL;
Kueri ini seharusnya menghasilkan keluaran berikut:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------- 10251 84 2017-07-08 654.06 -459.965000 10253 34 2017-07-10 1444.80 531.733334 10256 88 2017-07-15 517.80 -1022.020000 10266 87 2017-07-26 346.56 NULL 10273 63 2017-08-05 2037.28 -3149.075000 10283 46 2017-08-16 1414.80 534.300000 10309 37 2017-09-19 1762.00 -1951.262500 10321 38 2017-10-03 144.00 NULL 10330 46 2017-10-16 1649.00 885.600000 10332 51 2017-10-17 1786.88 495.830000 ...
Solusi dengan subkueri perlu menambahkan predikat filter dua kali—sekali di kueri luar dan sekali di subkueri—seperti:
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate AND empid = 3 AND shippeddate IS NOT NULL) AS diff FROM Sales.OrderValues AS O1 WHERE empid = 3 AND shippeddate IS NOT NULL;
Entah ini, atau menambahkan CTE awal yang menangani semua pemfilteran dan logika lainnya. Bagaimanapun Anda melihatnya, dengan subquery, ada lebih banyak lapisan kompleksitas yang terlibat.
Manfaat lain dalam fungsi jendela bersarang adalah jika kami memiliki dukungan untuk mereka yang ada di T-SQL, akan mudah untuk meniru dukungan penuh yang hilang untuk unit bingkai jendela RANGE. Opsi RANGE seharusnya memungkinkan Anda untuk menentukan bingkai dinamis yang didasarkan pada offset dari nilai pemesanan di baris saat ini. Misalnya, Anda perlu menghitung untuk setiap pesanan pelanggan dari Sales.OrderValues melihat nilai rata-rata bergerak dari 14 hari terakhir. Menurut standar SQL, Anda dapat mencapai ini menggunakan opsi RANGE dan tipe INTERVAL, seperti:
SELECT orderid, custid, orderdate, val, AVG(val) OVER( PARTITION BY custid ORDER BY orderdate RANGE BETWEEN INTERVAL '13' DAY PRECEDING AND CURRENT ROW ) AS movingavg14days FROM Sales.OrderValues;
Kueri ini seharusnya menghasilkan keluaran berikut:
orderid custid orderdate val movingavg14days -------- ------- ---------- ------- --------------- 10643 1 2018-08-25 814.50 814.500000 10692 1 2018-10-03 878.00 878.000000 10702 1 2018-10-13 330.00 604.000000 10835 1 2019-01-15 845.80 845.800000 10952 1 2019-03-16 471.20 471.200000 11011 1 2019-04-09 933.50 933.500000 10308 2 2017-09-18 88.80 88.800000 10625 2 2018-08-08 479.75 479.750000 10759 2 2018-11-28 320.00 320.000000 10926 2 2019-03-04 514.40 514.400000 10365 3 2017-11-27 403.20 403.200000 10507 3 2018-04-15 749.06 749.060000 10535 3 2018-05-13 1940.85 1940.850000 10573 3 2018-06-19 2082.00 2082.000000 10677 3 2018-09-22 813.37 813.370000 10682 3 2018-09-25 375.50 594.435000 10856 3 2019-01-28 660.00 660.000000 ...
Pada tanggal penulisan ini, sintaks ini tidak didukung di T-SQL. Jika kami memiliki dukungan untuk fungsi jendela bersarang di T-SQL, Anda akan dapat meniru kueri ini dengan kode berikut:
SELECT orderid, custid, orderdate, val, AVG( CASE WHEN DATEDIFF(day, orderdate, VALUE OF orderdate AT CURRENT_ROW) BETWEEN 0 AND 13 THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate RANGE UNBOUNDED PRECEDING ) AS movingavg14days FROM Sales.OrderValues;
Apa yang tidak disukai?
Berikan suara Anda
Fungsi jendela bersarang standar tampak seperti konsep yang sangat kuat yang memungkinkan banyak fleksibilitas dalam berinteraksi dengan berbagai titik dalam elemen jendela. Saya cukup terkejut bahwa saya tidak dapat menemukan cakupan konsep apa pun selain dalam standar itu sendiri, dan bahwa saya tidak melihat banyak platform yang menerapkannya. Semoga artikel ini dapat meningkatkan kesadaran akan fitur ini. Jika Anda merasa bahwa ketersediaannya di T-SQL berguna bagi Anda, pastikan untuk memberikan suara Anda!