Menerapkan fungsi jendela untuk analitik
Tip
Lihat tab Teks dan gambar untuk detail selengkapnya!
Kueri analitik sering memerlukan perhitungan yang mencakup beberapa baris sambil tetap mengembalikan detail baris individual. Fungsi agregat tradisional menciutkan baris ke dalam grup, kehilangan informasi tingkat baris. Fungsi jendela menyelesaikan tantangan ini dengan melakukan perhitungan di sekumpulan baris yang terkait dengan baris saat ini, tanpa menciutkan kumpulan hasil.
Memahami sintaks fungsi jendela
Fungsi jendela menghitung nilai di seluruh baris "window" yang ditentukan oleh klausa OVER. Tidak seperti fungsi agregat biasa, fungsi jendela tidak mengelompokkan baris ke dalam satu baris output. Sebaliknya, mereka menghitung nilai di seluruh baris terkait sambil mempertahankan semua baris asli dalam hasilnya.
Sintaks umum untuk fungsi jendela adalah:
function_name(arguments) OVER (
[PARTITION BY partition_expression]
[ORDER BY order_expression [ASC | DESC]]
[ROWS | RANGE frame_specification]
)
Komponen OVER klausa mengontrol bagaimana jendela didefinisikan:
- PARTITION BY: Membagi baris menjadi grup (partisi) untuk perhitungan
- ORDER BY: Menentukan urutan logis baris dalam setiap partisi
- ROWS/RANGE: Menentukan batas bingkai relatif terhadap baris saat ini
Kueri berikut menunjukkan fungsi jendela sederhana yang menghitung total jumlah pesanan yang berjalan per pelanggan:
SELECT
CustomerID,
SalesOrderID,
OrderDate,
TotalDue,
SUM(TotalDue) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS RunningTotal
FROM SalesLT.SalesOrderHeader
ORDER BY CustomerID, OrderDate;
Nota
Saat Anda menentukan ORDER BY dalam OVER klausul tanpa spesifikasi bingkai, bingkai defaultnya adalah RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW untuk fungsi agregat. Ini membuat perhitungan kumulatif.
Menggunakan fungsi peringkat
Fungsi peringkat menetapkan angka berurutan ke baris berdasarkan posisinya dalam partisi. SQL Server menyediakan empat fungsi peringkat. Setiap fungsi menangani ikatan secara berbeda:
ROW_NUMBER() menetapkan angka berurutan unik untuk setiap baris, tanpa duplikat bahkan untuk nilai terikat:
SELECT
ProductID,
Name,
ListPrice,
ROW_NUMBER() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 2
751 Road-150 Red, 48 3578.27 3
771 Mountain-100 Silver, 38 3399.99 4
Kueri ini memberi peringkat semua produk berdasarkan harga dari tertinggi ke terendah. Setiap produk menerima angka unik terlepas dari apakah beberapa produk memiliki harga yang sama.
RANK() menetapkan peringkat yang sama ke nilai terikat, lalu melompati angka untuk memperhitungkan ikatan:
SELECT
ProductID,
Name,
ListPrice,
RANK() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 1
751 Road-150 Red, 48 3578.27 1
771 Mountain-100 Silver, 38 3399.99 4
Ketika dua produk memiliki harga yang identik, keduanya menerima peringkat yang sama. Peringkat produk berikutnya mencerminkan jumlah total produk yang diberi peringkat lebih tinggi, menciptakan celah dalam urutan.
DENSE_RANK() menetapkan peringkat yang sama ke nilai terikat tetapi tidak melewati angka:
SELECT
ProductID,
Name,
ListPrice,
DENSE_RANK() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 1
751 Road-150 Red, 48 3578.27 1
771 Mountain-100 Silver, 38 3399.99 2
Seperti RANK(), nilai terikat memiliki peringkat yang sama. Namun, DENSE_RANK() berlanjut dengan angka berturut-turut berikutnya, sehingga Anda dapat menggunakannya untuk menghitung tingkat harga yang berbeda.
NTILE(n) mendistribusikan baris ke dalam jumlah grup yang ditentukan yang kira-kira sama:
SELECT
ProductID,
Name,
ListPrice,
NTILE(4) OVER (ORDER BY ListPrice DESC) AS PriceQuartile
FROM SalesLT.Product
WHERE ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
ProductID Name ListPrice PriceQuartile
--------- --------------------------- --------- -------------
749 Road-150 Red, 62 3578.27 1
771 Mountain-100 Silver, 38 3399.99 1
722 LL Road Frame - Black, 58 337.22 2
859 Half-Finger Gloves, S 24.49 4
Kueri ini membagi produk menjadi empat grup berdasarkan harga. Produk dengan harga tertinggi berada di kuartil 1, dan harga terendah berada di kuartil 4. Gunakan NTILE() untuk analisis persentil atau mendistribusikan pekerjaan secara merata.
Menggabungkan PARTITION BY dengan fungsi peringkat memungkinkan peringkat per grup.
SELECT
pc.Name AS Category,
p.Name AS Product,
p.ListPrice,
ROW_NUMBER() OVER (
PARTITION BY p.ProductCategoryID
ORDER BY p.ListPrice DESC
) AS CategoryPriceRank
FROM SalesLT.Product AS p
INNER JOIN SalesLT.ProductCategory AS pc
ON p.ProductCategoryID = pc.ProductCategoryID
WHERE p.ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
Category Product ListPrice CategoryPriceRank
--------------- ------------------------- --------- -----------------
Road Bikes Road-150 Red, 62 3578.27 1
Road Bikes Road-150 Red, 44 3578.27 2
Mountain Bikes Mountain-100 Silver, 38 3399.99 1
Mountain Bikes Mountain-100 Black, 38 3374.99 2
Kueri ini memberi peringkat produk dalam setiap kategori secara terpisah. Peringkat dimulai ulang pada 1 untuk setiap kategori, sehingga Anda dapat mengidentifikasi produk termahal di setiap kategori dengan memfilter untuk CategoryPriceRank = 1.
Tip
Gunakan ROW_NUMBER() saat Anda membutuhkan tepat satu baris per peringkat (seperti menemukan N teratas per grup). Gunakan RANK() atau DENSE_RANK() saat Anda perlu mempertahankan informasi tautan untuk tujuan pelaporan.
Menerapkan fungsi jendela analisis agregat
Fungsi agregat standar seperti SUM, , AVGCOUNT, MIN, dan MAX dapat digunakan sebagai fungsi jendela dengan menambahkan OVER klausa. Ini memungkinkan Anda menghitung agregat sambil mempertahankan detail baris individual.
Kueri berikut menunjukkan cara menghitung total yang berjalan dan agregat kumulatif:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
SUM(TotalDue) OVER (ORDER BY OrderDate, SalesOrderID) AS RunningTotal,
AVG(TotalDue) OVER (ORDER BY OrderDate, SalesOrderID) AS RunningAverage,
COUNT(*) OVER (ORDER BY OrderDate, SalesOrderID) AS OrderNumber
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate, SalesOrderID;
Kumpulan hasil terlihat seperti ini:
SalesOrderID OrderDate TotalDue RunningTotal RunningAverage OrderNumber
------------ ---------- --------- ------------ -------------- -----------
71774 2008-06-01 972.785 972.785 972.785 1
71776 2008-06-01 87.083 1059.868 529.934 2
71780 2008-06-01 42452.65 43512.518 14504.172 3
71782 2008-06-01 43962.79 87475.308 21868.827 4
Penting
Saat menggunakan fungsi jendela agregat tanpa ORDER BY dalam OVER klausa, fungsi menghitung di seluruh partisi. Menambahkan ORDER BY membuat penghitungan berjalan dari awal partisi ke baris saat ini.
Tentukan bingkai jendela dengan ROWS dan RANGE
Bingkai jendela memungkinkan Anda menentukan dengan tepat baris mana yang relatif terhadap baris saat ini harus disertakan dalam perhitungan. Klausa ROWS menghitung baris fisik, sementara RANGE mengelompokkan baris dengan nilai yang sama.
Batas bingkai dapat ditentukan menggunakan:
-
UNBOUNDED PRECEDING: Dari awal partisi -
n PRECEDING:nbaris sebelum baris ini -
CURRENT ROW: Baris saat ini -
n FOLLOWING:nbaris setelah baris saat ini -
UNBOUNDED FOLLOWING: Ke akhir partisi
Kueri berikut menghitung rata-rata bergerak selama tiga pesanan terakhir:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
AVG(TotalDue) OVER (
ORDER BY OrderDate
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS MovingAvg3Orders
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate;
Kumpulan hasil terlihat seperti ini:
SalesOrderID OrderDate TotalDue MovingAvg3Orders
------------ ---------- --------- ----------------
71774 2008-06-01 972.785 972.785
71776 2008-06-01 87.083 529.934
71780 2008-06-01 42452.65 14504.172
71782 2008-06-01 43962.79 28834.174
Kueri ini menghitung rata-rata bergerak orde ke-3 dengan mencakup baris saat ini dan dua baris sebelumnya. Untuk baris pertama, hanya satu nilai yang tersedia, sehingga rata-rata sama dengan TotalDue. Pada saat mencapai baris ketiga, jendela sudah mencakup ketiga baris tersebut.
Menggunakan fungsi analitik
Fungsi analitik memungkinkan Anda mengakses data dari baris lain tanpa menggunakan gabungan atau subkueri mandiri. Fungsi-fungsi ini berguna untuk analisis rangkaian waktu, deteksi tren, dan membandingkan nilai saat ini dengan nilai historis atau masa depan. Tidak seperti fungsi jendela agregat yang menghitung ringkasan, fungsi analitik mengambil nilai tertentu dari baris tertentu di jendela.
LAG() dan LEAD() mengakses nilai dari baris sebelumnya atau berikutnya, seperti ini:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
LAG(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS PreviousOrderTotal,
LEAD(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS NextOrderTotal,
TotalDue - LAG(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS ChangeFromPrevious
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate;
Kumpulan hasil terlihat seperti ini:
SalesOrderID OrderDate TotalDue PreviousOrderTotal NextOrderTotal ChangeFromPrevious
------------ ---------- --------- ------------------ -------------- ------------------
71774 2008-06-01 972.785 0 87.083 972.785
71776 2008-06-01 87.083 972.785 42452.65 -885.702
71780 2008-06-01 42452.65 87.083 43962.79 42365.567
71782 2008-06-01 43962.79 42452.65 0 1510.14
LAG() mengambil nilai dari baris sebelumnya, sementara LEAD() mengambil dari baris berikut. Parameter kedua menentukan berapa banyak baris yang akan dilihat kembali atau di depan (defaultnya adalah 1), dan parameter ketiga memberikan nilai default ketika tidak ada baris (seperti untuk baris pertama dengan LAG()). Gunakan fungsi-fungsi ini untuk menghitung perubahan periode berlebih, mengidentifikasi tren, atau mendeteksi anomali dalam data berurutan.
FIRST_VALUE() dan LAST_VALUE() mengembalikan nilai dari baris pertama atau terakhir dalam bingkai:
SELECT
ProductID,
Name,
ListPrice,
ProductCategoryID,
FIRST_VALUE(Name) OVER (
PARTITION BY ProductCategoryID
ORDER BY ListPrice DESC
) AS MostExpensiveInCategory,
LAST_VALUE(Name) OVER (
PARTITION BY ProductCategoryID
ORDER BY ListPrice DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS LeastExpensiveInCategory
FROM SalesLT.Product
WHERE ListPrice > 0;
Kumpulan hasil terlihat seperti ini:
ProductID Name ListPrice ProductCategoryID MostExpensiveInCategory LeastExpensiveInCategory
--------- ------------------------- --------- ----------------- ------------------------ ------------------------
749 Road-150 Red, 62 3578.27 5 Road-150 Red, 62 LL Road Frame - Red, 58
750 Road-150 Red, 44 3578.27 5 Road-150 Red, 62 LL Road Frame - Red, 58
722 LL Road Frame - Red, 58 337.22 5 Road-150 Red, 62 LL Road Frame - Red, 58
771 Mountain-100 Silver, 38 3399.99 6 Mountain-100 Silver, 38 Mountain-500 Black, 52
FIRST_VALUE() mengembalikan nilai dari baris pertama di jendela yang diurutkan, yang dalam hal ini adalah produk termahal per kategori.
LAST_VALUE() mengembalikan yang paling murah, tetapi memerlukan bingkai eksplisit untuk menyertakan semua baris. Fungsi-fungsi ini membantu Anda membandingkan setiap baris dengan nilai tolok ukur seperti nilai tertinggi, terendah, atau garis besar dalam grup.
Nota
LAST_VALUE() memerlukan spesifikasi bingkai eksplisit untuk menyertakan baris setelah baris saat ini. Tanpa itu, bingkai default hanya menyertakan baris hingga baris saat ini, membuat LAST_VALUE() mengembalikan nilai baris saat ini.
PERCENT_RANK() dan CUME_DIST() menghitung posisi relatif dalam partisi:
SELECT
Name,
ListPrice,
PERCENT_RANK() OVER (ORDER BY ListPrice) AS PercentRank,
CUME_DIST() OVER (ORDER BY ListPrice) AS CumulativeDistribution
FROM SalesLT.Product
WHERE ListPrice > 0
ORDER BY ListPrice;
Kumpulan hasil terlihat seperti ini:
Name ListPrice PercentRank CumulativeDistribution
------------------------- --------- ----------- ----------------------
Patch Kit/8 Patches 2.29 0.0 0.0081
Road Tire Tube 3.99 0.0081 0.0162
Touring Tire Tube 4.99 0.0162 0.0243
Road-150 Red, 62 3578.27 0.9919 1.0
PERCENT_RANK() mengembalikan nilai antara 0 dan 1 yang menunjukkan persentase baris yang memiliki nilai yang lebih rendah (0 berarti terendah, satu berarti tertinggi).
CUME_DIST() memperlihatkan distribusi kumulatif, menunjukkan persentase baris apa yang memiliki nilai kurang dari atau sama dengan baris saat ini. Gunakan fungsi-fungsi ini untuk analisis persentil, mengidentifikasi outlier, atau membuat laporan distribusi.
Untuk informasi selengkapnya tentang fungsi jendela, lihat Fungsi Jendela (Transact-SQL) dan Fungsi Peringkat.