
Foto oleh panumas nikhomkhai via Pexels
Window function adalah kelompok fungsi di SQL Server yang memungkinkan perhitungan dilakukan terhadap sekumpulan baris terkait (disebut 'window') tanpa mengurangi jumlah baris pada hasil akhir, berbeda dengan GROUP BY yang meringkas banyak baris menjadi satu baris per kelompok.
Tiga window function yang paling sering dipakai adalah ROW_NUMBER, RANK, dan DENSE_RANK, yang semuanya memberi nomor urut pada baris berdasarkan urutan tertentu, namun berbeda cara menangani nilai yang sama persis (seri/tie). Ditambah dengan PARTITION BY, perhitungan ini bisa dipecah ulang untuk setiap kelompok data tanpa kehilangan baris detail aslinya.
Memahami perbedaan ketiga fungsi ini serta cara kerja PARTITION BY sangat penting karena window function sering dipakai untuk kebutuhan seperti mengambil transaksi terbaru per pelanggan, memberi peringkat produk terlaris per kategori, atau menghitung running total.
ROW_NUMBER untuk Memberi Nomor Urut Unik
ROW_NUMBER dengan PARTITION BY per pelanggan
RANK dan DENSE_RANK Menangani Nilai Seri
Perbandingan RANK dan DENSE_RANK pada nilai seri
Menghitung Running Total dengan SUM OVER
SUM OVER menghasilkan running total tanpa GROUP BY
Mengambil Baris Pertama per Kelompok dengan Subquery
Membungkus ROW_NUMBER dalam subquery untuk difilter
Window Function LAG LEAD dan Running Total dengan OVER
LAG/LEAD dan running total menggunakan frame ROWS BETWEEN
LAG() dan LEAD() memungkinkan satu baris mengakses nilai kolom dari baris sebelumnya atau berikutnya dalam urutan yang ditentukan ORDER BY, tanpa perlu melakukan self-join yang biasanya diperlukan pada pendekatan SQL konvensional. Fungsi ini sangat berguna untuk menghitung selisih periode ke periode, mendeteksi perubahan status berurutan, atau membandingkan transaksi terakhir pelanggan dengan transaksi sebelumnya.
Klausa frame seperti ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW menentukan batas baris mana saja yang ikut dihitung dalam agregasi window function untuk setiap baris; tanpa frame eksplisit, SQL Server memakai default ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ketika ada ORDER BY, yang menghasilkan running total kumulatif seperti pada contoh, bukan total keseluruhan partisi.
Perbedaan mendasar antara window function dan agregasi GROUP BY biasa adalah window function tidak meringkas baris menjadi satu baris per grup, melainkan tetap menampilkan setiap baris detail sambil menyertakan nilai agregat yang relevan di sampingnya, sehingga baris mentah dan hasil perhitungan analitis bisa dilihat bersamaan dalam satu hasil query.
RANK DENSE_RANK dan ROW_NUMBER Saat Menangani Data Seri (Ties)
Tiga fungsi ranking menangani baris dengan nilai sama secara berbeda
ROW_NUMBER() selalu memberikan nomor urut yang unik untuk setiap baris dalam partisi, bahkan ketika dua baris memiliki nilai ORDER BY yang persis sama; urutan di antara baris yang nilainya sama ini sebenarnya tidak deterministik kecuali ORDER BY diperjelas dengan kolom tambahan sebagai tie-breaker, sehingga menjalankan query yang sama dua kali berpotensi menghasilkan urutan berbeda untuk baris-baris yang nilainya identik.
RANK() memberikan nomor yang sama untuk baris-baris dengan nilai identik, namun nomor berikutnya akan melompati sejumlah baris yang memiliki rank sama tersebut, seperti terlihat pada contoh di mana Produk C mendapat Rnk 3 bukan 2 karena ada dua baris di atasnya yang berbagi Rnk 1. DENSE_RANK() menyelesaikan masalah lompatan ini dengan memberikan nomor berikutnya secara berurutan tanpa celah, sehingga Produk C mendapat DenseRnk 2.
Pemilihan fungsi yang tepat bergantung pada kebutuhan bisnis: ROW_NUMBER cocok ketika setiap baris memang harus diberi identitas urutan unik, misalnya untuk keperluan paging data; RANK cocok ketika posisi harus mencerminkan jumlah pesaing di atasnya seperti peringkat kompetisi; dan DENSE_RANK cocok ketika yang dibutuhkan adalah jumlah tingkatan nilai yang berbeda, misalnya mengelompokkan produk ke dalam tier penjualan tanpa peduli berapa banyak produk di tiap tier. Memakai ROW_NUMBER untuk kebutuhan yang sebenarnya menuntut penanganan seri yang adil, seperti menentukan top-N per kategori tanpa menghilangkan produk yang nilainya sama, justru dapat menghasilkan laporan yang keliru.
NTILE dan PERCENT_RANK untuk Membagi Data ke dalam Kelompok Persentil
Membagi customer ke dalam kuartil dengan NTILE dan posisi relatif dengan PERCENT_RANK
NTILE(n) membagi baris dalam partisi menjadi n kelompok dengan jumlah baris sedekat mungkin sama rata, sehingga cocok dipakai untuk segmentasi seperti membagi customer ke kuartil berdasarkan total belanja; jika jumlah baris tidak habis dibagi n, kelompok-kelompok awal akan mendapat satu baris ekstra dibanding kelompok berikutnya.
PERCENT_RANK() menghasilkan posisi relatif setiap baris dalam rentang 0 sampai 1 berdasarkan rumus (rank-1) dibagi (jumlah baris-1), sehingga nilainya menunjukkan persentase baris lain yang berada di bawah baris tersebut. Berbeda dari NTILE yang membagi ke kelompok diskret, PERCENT_RANK memberi ukuran posisi yang kontinu dan berguna untuk analisis seperti menentukan customer berada di persentil ke berapa. Kedua fungsi ini sering dipakai bersamaan pada laporan segmentasi, di mana NTILE menentukan label kelompok dan PERCENT_RANK memberi angka presisi untuk pengurutan lanjutan di dalam kelompok yang sama.
Kesalahan Umum yang Sering Terjadi
Memakai Window Function Langsung di WHERE
SQL Server tidak mengizinkan window function dipakai langsung pada klausa WHERE di level query yang sama karena urutan evaluasi klausa; solusinya harus dibungkus subquery atau CTE terlebih dahulu.
Lupa Menyertakan PARTITION BY Saat Butuh Per Kelompok
Tanpa PARTITION BY, window function akan menghitung terhadap seluruh hasil query sebagai satu kelompok besar, bukan per kelompok seperti per pelanggan atau per kategori yang sebenarnya diinginkan.
Menggunakan RANK Padahal Butuh Nomor Urut Unik
Jika tujuannya memastikan setiap baris punya nomor berbeda, RANK bisa menghasilkan nomor yang sama pada baris seri sehingga filter 'ambil baris nomor 1' bisa mengembalikan lebih dari satu baris tanpa disadari.
Kapan Pendekatan Ini Tidak Cocok Dipakai
Window function sangat efisien untuk kebutuhan analitis seperti peringkat dan running total karena dihitung dalam satu pass data tanpa perlu self-join, namun ia membutuhkan versi SQL Server yang mendukungnya (2012 ke atas untuk sebagian besar fungsi) dan pemahaman yang lebih mendalam dibanding GROUP BY biasa.
Untuk kebutuhan yang memang hanya butuh satu baris ringkasan per kelompok, GROUP BY tetap lebih sederhana dan lebih mudah dibaca; window function unggul justru ketika baris detail aslinya tetap harus dipertahankan berdampingan dengan hasil perhitungannya.
Ringkasan
ROW_NUMBER, RANK, dan DENSE_RANK memberi cara yang bersih untuk memberi nomor urut atau peringkat per kelompok tanpa kehilangan baris detail, sementara PARTITION BY menentukan batas setiap kelompok perhitungan tersebut. Pahami perbedaan penanganan nilai seri sebelum memilih fungsi mana yang dipakai, karena pilihan yang salah bisa menghasilkan data yang terlihat benar namun sebenarnya keliru.
Seri Belajar SQL Server:
← Sebelumnya: Common Table Expression (CTE) di SQL Server
→ Selanjutnya: Membuat dan Menggunakan VIEW di SQL Server