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

Fungsi jendela bersarang di SQL

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:

(ROW_NUMBER()>) LEBIH()

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:

( NILAI DI [] [, ]
>) 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!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Hancurkan Tembok! Cara Unsilo Data Anda

  2. Bagaimana cara menginstal SQLcl di windows?

  3. Pengenalan SQL

  4. Cadangan hanya basis data di WHM

  5. Sinkronisasi struktur database antar aplikasi