
Foto oleh panumas nikhomkhai via Pexels
Memahami Window Function ROW_NUMBER, RANK, dan DENSE_RANK di SQL Server
Ketika Anda perlu menganalisis performa karyawan berdasarkan penjualan bulanan, menentukan posisi juara dalam kompetisi, atau mencari data unik seperti transaksi terbaru dari setiap pelanggan, tantangan utamanya adalah bagaimana memberikan peringkat yang akurat pada setiap baris data. Melakukan ini dengan subquery atau GROUP BY bisa jadi rumit dan tidak efisien, terutama saat Anda perlu memperhitungkan skenario "seri" atau *ties*.
Di artikel sebelumnya, kita sudah membahas konsep dasar Window Functions, termasuk ROW_NUMBER dan RANK beserta pentingnya klausa PARTITION BY. Kali ini, kita akan mendalami lebih lanjut ketiga fungsi ranking utama ini—ROW_NUMBER, RANK, dan DENSE_RANK—beserta perbedaan fundamental dan kapan sebaiknya menggunakannya untuk skenario data yang berbeda. Pemahaman ini krusial untuk menghasilkan laporan yang presisi dan analisis yang mendalam.
Mari kita siapkan data penjualan sederhana untuk demonstrasi:
Menyiapkan Data Demonstrasi
Kita akan menggunakan tabel Penjualan yang berisi data transaksi karyawan setiap bulannya. Data ini akan membantu kita memahami bagaimana setiap fungsi ranking bekerja dalam konteks yang realistis.
Mendefinisikan skema tabel Penjualan
Dan kita isi dengan beberapa data sampel, termasuk skenario penjualan yang sama antar karyawan untuk menyoroti perbedaan antar fungsi ranking:
Menyisipkan data contoh ke tabel Penjualan
ROW_NUMBER(): Penomoran Baris Unik
Fungsi ROW_NUMBER() memberikan nomor urut yang unik dan berurutan untuk setiap baris di dalam sebuah "window" atau partisi. Nomor urut ini dimulai dari 1. Poin pentingnya adalah, ROW_NUMBER() tidak memperdulikan nilai data yang sama; setiap baris akan mendapatkan nomor yang berbeda, bahkan jika semua kolom lain identik dalam partisi tersebut. Ini sangat berguna ketika Anda ingin mengambil satu baris spesifik (misalnya, yang terbaru, terlama, atau tertinggi) dari setiap grup.
Kapan Menggunakan ROW_NUMBER?
- Mengambil N baris pertama/terakhir dari setiap kategori (misal: 3 produk terlaris di setiap kategori).
- Mendapatkan record terbaru atau terlama dari suatu entitas (misal: transaksi terakhir setiap pelanggan).
- Menghilangkan duplikasi jika semua kolom relevan sama, dengan mempertahankan satu record berdasarkan kriteria tertentu (misal: ID terkecil/terbesar).
Contoh ROW_NUMBER
Untuk melihat karyawan dengan penjualan tertinggi pertama setiap bulannya:
Contoh penggunaan ROW_NUMBER() untuk peringkat penjualan bulanan
Perhatikan hasilnya. Meskipun Budi Santoso dan Andi Wijaya memiliki penjualan yang sama di Januari, ROW_NUMBER() tetap memberikan nomor urut yang berbeda (1 dan 2) karena ia hanya menghitung posisi baris secara sequential setelah diurutkan.
RANK(): Peringkat dengan Lompatan
Fungsi RANK() memberikan peringkat untuk setiap baris dalam partisi. Jika ada dua atau lebih baris yang memiliki nilai yang sama (berdasarkan klausa ORDER BY), mereka akan mendapatkan peringkat yang sama. Namun, RANK() akan "melompati" peringkat berikutnya. Misalnya, jika ada dua baris di peringkat 1, maka peringkat berikutnya akan menjadi 3 (melewati peringkat 2).
Kapan Menggunakan RANK?
- Memberikan peringkat dalam kompetisi di mana ada kemungkinan hasil seri, dan Anda ingin ada lompatan peringkat.
- Menentukan top N item di mana posisi "ke N" bisa diisi oleh beberapa item yang seri.
Contoh RANK
Mari kita lihat peringkat karyawan bulanan menggunakan RANK():
Contoh penggunaan RANK() untuk peringkat penjualan bulanan
Di bulan Januari, Budi Santoso dan Andi Wijaya sama-sama mendapat RankPenjualan 2. Karena ada dua karyawan di peringkat 2, peringkat berikutnya yang tersedia adalah 4 (peringkat 3 dilewati). Ini sesuai dengan perilaku RANK().
DENSE_RANK(): Peringkat Tanpa Lompatan
Mirip dengan RANK(), fungsi DENSE_RANK() juga memberikan peringkat yang sama untuk baris dengan nilai yang identik. Perbedaannya terletak pada penomoran peringkat selanjutnya. DENSE_RANK() tidak akan melompati peringkat. Jika ada dua baris di peringkat 1, maka peringkat berikutnya akan menjadi 2, bukan 3.
Kapan Menggunakan DENSE_RANK?
- Membuat peringkat di mana Anda ingin urutan peringkat tetap berurutan tanpa "gap", meskipun ada seri.
- Menentukan level atau tingkatan performa di mana setiap level harus memiliki nomor yang berurutan.
- Leaderboard atau daftar penghargaan di mana jumlah posisi yang berbeda (distinct ranks) adalah yang terpenting.
Contoh DENSE_RANK
Sekarang, mari kita terapkan DENSE_RANK() pada data penjualan yang sama:
Contoh penggunaan DENSE_RANK() untuk peringkat penjualan bulanan
Kali ini, Budi Santoso dan Andi Wijaya sama-sama mendapat DenseRankPenjualan 2. Namun, peringkat berikutnya adalah 3, bukan 4. Ini menunjukkan bahwa DENSE_RANK() mempertahankan urutan peringkat yang padat tanpa ada celah.
Perbandingan Ketiga Fungsi Ranking
Untuk memahami perbedaan ketiganya secara lebih jelas, mari kita jalankan ketiga fungsi tersebut dalam satu query yang sama:
Perbandingan ROW_NUMBER, RANK, dan DENSE_RANK dalam satu query
Dari hasil di atas, Anda bisa melihat bagaimana ketiga fungsi ini menangani baris dengan nilai TotalPenjualan yang sama (misalnya, Budi dan Andi, atau Citra dan Eko di bulan Januari) secara berbeda:
ROW_NUMBER: Setiap baris memiliki nomor unik. Tidak ada seri, tidak ada lompatan.RANK: Baris yang seri mendapatkan peringkat yang sama, dan peringkat berikutnya dilompati.DENSE_RANK: Baris yang seri mendapatkan peringkat yang sama, dan peringkat berikutnya tidak dilompati (berurutan padat).
Klausa OVER (PARTITION BY dan ORDER BY)
Penting untuk diingat bahwa ketiga fungsi ini selalu memerlukan klausa OVER() yang di dalamnya harus ada ORDER BY. Klausa PARTITION BY bersifat opsional, tetapi sangat umum digunakan.
PARTITION BY: Mendefinisikan "jendela" atau grup di mana fungsi ranking akan diterapkan. Misalnya,PARTITION BY BulanTransaksiberarti ranking akan dihitung ulang untuk setiap bulan. Jika tidak adaPARTITION BY, seluruh dataset akan dianggap sebagai satu partisi.ORDER BY: Menentukan kriteria pengurutan di dalam setiap partisi. Ini krusial karena ranking didasarkan pada urutan data. TanpaORDER BY, fungsi ranking tidak tahu bagaimana mengurutkan data untuk memberikan peringkat.
Kesalahan Umum dan Pertimbangan
Meskipun Window Function sangat powerful, ada beberapa kesalahan umum dan pertimbangan yang perlu diperhatikan:
Tidak Menggunakan ORDER BY
Ini adalah kesalahan fatal. Klausa
OVER()*wajib* memilikiORDER BYuntuk fungsi ranking. TanpaORDER BY, SQL Server tidak bisa menentukan urutan data untuk memberikan peringkat. Hasilnya bisa error atau tidak konsisten jika diizinkan oleh versi SQL Server tertentu.Contoh kesalahan: Lupa ORDER BY dalam klausa OVER()
SELECT IDKaryawan, NamaKaryawan, BulanTransaksi, TotalPenjualan, -- Ini akan menghasilkan error atau hasil yang tidak terprediksi di beberapa versi SQL Server -- ROW_NUMBER() OVER (PARTITION BY BulanTransaksi) AS RankPenjualan FROM Penjualan;Kesalahan dalam PARTITION BY
Jika Anda lupa menambahkan
PARTITION BYatau menentukannya dengan kolom yang salah, ranking akan dihitung untuk seluruh dataset alih-alih per grup yang Anda inginkan. Misalnya, jika ingin ranking per bulan tapi lupaPARTITION BY BulanTransaksi, maka ranking akan global untuk semua transaksi.Contoh kesalahan: Ranking global bukan per bulan karena salah PARTITION BY
SELECT IDKaryawan, NamaKaryawan, BulanTransaksi, TotalPenjualan, -- Ranking ini akan dihitung untuk seluruh dataset, bukan per bulan ROW_NUMBER() OVER (ORDER BY TotalPenjualan DESC) AS GlobalRowNum FROM Penjualan ORDER BY BulanTransaksi, GlobalRowNum;Memilih Fungsi yang Salah untuk Skenario Tie
Pastikan Anda memilih
RANK()atauDENSE_RANK()sesuai dengan bagaimana Anda ingin menangani seri dan apakah Anda ingin adanya lompatan peringkat atau tidak. Memahami perbedaan mendasar ini akan mencegah hasil laporan yang salah interpretasi.Dampak Performa pada Data Besar
Window functions, terutama pada tabel dengan jumlah data sangat besar dan banyak partisi, bisa memakan sumber daya CPU dan memori. Pastikan Anda memiliki indeks yang tepat pada kolom yang digunakan di
PARTITION BYdanORDER BYuntuk optimasi. Dalam skenario kompleks, gabungan dengan Common Table Expression (CTE) (seperti yang pernah dibahas di artikel CTE vs Temp Table: Kapan Sebaiknya Digunakan) dapat membantu memecah query menjadi lebih mudah dibaca dan terkadang lebih efisien dengan materialisasi sebagian hasil.
Kesimpulan
Fungsi ROW_NUMBER(), RANK(), dan DENSE_RANK() adalah alat yang tak ternilai dalam toolkit SQL Anda untuk melakukan analisis data yang kompleks dan memberikan peringkat yang presisi. Setiap fungsi memiliki karakteristik unik dalam menangani urutan dan seri:
ROW_NUMBER(): Memberikan nomor urut unik, tidak ada seri, tidak ada lompatan. Ideal untuk mengambil "satu terbaik" per grup.RANK(): Memberikan peringkat yang sama untuk baris yang seri, dengan lompatan pada peringkat berikutnya. Cocok untuk skenario di mana lompatan peringkat diperlukan.DENSE_RANK(): Memberikan peringkat yang sama untuk baris yang seri, tanpa melompati peringkat berikutnya. Ideal untuk peringkat yang padat dan berurutan.
Memilih fungsi yang tepat bergantung pada kebutuhan bisnis dan bagaimana Anda ingin hasil seri diperlakukan. Dengan pemahaman yang solid mengenai perbedaan ketiganya, Anda bisa menulis query SQL yang lebih akurat, efisien, dan powerful untuk berbagai kebutuhan pelaporan dan analisis data.
Seri Belajar SQL Server:
← Sebelumnya: CTE vs Temp Table: Kapan Sebaiknya Digunakan
→ Selanjutnya: LAG dan LEAD untuk Membandingkan Data Antar Baris