RANK vs DENSE_RANK SQL: Bedanya di Angka Kembar
TL;DR
RANK dan DENSE_RANK sama-sama ngasih nomor urut di SQL, bedanya cuma waktu ada nilai kembar. Kalau dua baris seri di posisi 1, RANK ngasih 1, 1, lalu loncat ke 3. DENSE_RANK ngasih 1, 1, lalu 2 — nomornya rapat tanpa loncatan. Pakai RANK kalau kamu mau posisi mencerminkan berapa banyak yang di atasnya. Pakai DENSE_RANK kalau kamu mau ngitung tingkatan nilai yang unik, misalnya cari 3 harga tertinggi.
RANK dan DENSE_RANK sama-sama ngasih nomor urut di SQL. Bedanya cuma muncul waktu ada nilai kembar.
Kalau dua produk laku sama banyak dan sama-sama di posisi 1, RANK ngasih nomor berikutnya 3. DENSE_RANK ngasih 2.
Itu doang bedanya. Tapi salah pilih bisa bikin laporan "Top 3" kamu isinya cuma 2 baris.
Apa bedanya RANK dan DENSE_RANK?
RANK ngasih nomor sama ke baris yang seri, terus ngeloncat sesuai jumlah yang seri. Nilai 100, 100, 90 dapat rank 1, 1, 3.
DENSE_RANK ngasih nomor sama ke baris yang seri, terus lanjut tanpa loncat. Nilai 100, 100, 90 dapat rank 1, 1, 2.
Kalau gak ada nilai kembar sama sekali, dua-duanya ngasih hasil yang persis sama. Bedanya baru kelihatan begitu ada seri.
Lihat langsung di angka
Tabel produk_terlaris dari dataset toko_berkah:
SELECT
nama_produk,
total_terjual,
ROW_NUMBER() OVER (ORDER BY total_terjual DESC) AS row_num,
RANK() OVER (ORDER BY total_terjual DESC) AS rank_biasa,
DENSE_RANK() OVER (ORDER BY total_terjual DESC) AS rank_rapat
FROM produk_terlaris
ORDER BY total_terjual DESC;
Hasilnya:
| nama_produk | total_terjual | row_num | rank_biasa | rank_rapat |
|---|---|---|---|---|
| Beras Pandan Wangi 5kg | 1.240 | 1 | 1 | 1 |
| Minyak Goreng 2L | 980 | 2 | 2 | 2 |
| Gula Pasir 1kg | 980 | 3 | 2 | 2 |
| Telur Ayam 1kg | 870 | 4 | 4 | 3 |
| Tepung Terigu 1kg | 720 | 5 | 5 | 4 |
Perhatiin baris Telur Ayam. RANK ngasih dia nomor 4 (soalnya ada 3 produk di atasnya). DENSE_RANK ngasih 3 (soalnya cuma ada 2 tingkatan nilai di atasnya).
ROW_NUMBER gak peduli seri sama sekali — dia kasih nomor unik 1 sampai 5 apa pun yang terjadi.
Kapan pakai RANK?
Pakai RANK kalau nomor urutnya harus mencerminkan berapa banyak baris yang di atasnya.
Contoh nyata: peringkat cabang berdasarkan omzet, buat dilaporin ke manajemen. Kalau ada 2 cabang seri di posisi 2, cabang berikutnya emang posisi 4 — ada 3 cabang yang omzetnya lebih tinggi atau sama.
Ini logika yang sama kayak peringkat lomba. Dua juara 2, gak ada juara 3.
Kapan pakai DENSE_RANK?
Pakai DENSE_RANK kalau kamu ngitung tingkatan nilai yang unik, bukan jumlah baris.
Contoh: "kasih diskon buat produk di 3 tier harga tertinggi". Yang kamu cari 3 tingkatan harga, bukan 3 baris.
Kalau kamu pakai RANK di sini, dan ada 2 produk yang harganya persis sama di tier 2, hasil WHERE rk <= 3 kamu bakal ngelewatin satu tier harga — soalnya nomornya loncat dari 2 ke 4.
Kapan pakai ROW_NUMBER?
Pakai ROW_NUMBER kalau kamu butuh tepat satu baris per grup, dan gak peduli ada yang seri.
Kasus klasik: hapus duplikat, atau ambil transaksi terbaru per pelanggan.
WITH terbaru AS (
SELECT
id_pelanggan,
id_transaksi,
tanggal,
ROW_NUMBER() OVER (
PARTITION BY id_pelanggan
ORDER BY tanggal DESC
) AS rn
FROM transaksi
)
SELECT id_pelanggan, id_transaksi, tanggal
FROM terbaru
WHERE rn = 1;
Kalau kamu pakai RANK di sini dan ada pelanggan yang punya 2 transaksi di tanggal yang sama, kamu bakal dapet 2 baris buat pelanggan itu. ROW_NUMBER mastiin cuma 1.
Contoh kasus: top 3 produk per kota
Ini kasus yang paling sering muncul di kerjaan nyata. Dataset toko_berkah punya 8.400 transaksi dari 12 cabang di 6 kota.
WITH ranked AS (
SELECT
kota,
nama_produk,
SUM(total) AS omzet,
DENSE_RANK() OVER (
PARTITION BY kota
ORDER BY SUM(total) DESC
) AS rk
FROM transaksi
GROUP BY kota, nama_produk
)
SELECT kota, nama_produk, omzet, rk
FROM ranked
WHERE rk <= 3
ORDER BY kota, rk;
PARTITION BY kota bikin nomor urutnya reset tiap ganti kota. Jadi tiap kota punya top 3 sendiri.
Hasil dari dataset ngulikdata: 4 dari 6 kota punya Beras Pandan Wangi di posisi 1. Dua kota yang beda — Denpasar dan Surabaya — posisi 1-nya diisi Minyak Goreng 2L.
Insight ini yang bikin tim procurement nyetok beras beda-beda per kota.
Kenapa window function gak bisa ditaruh di WHERE?
Ini kesalahan nomor satu yang aku lihat.
-- INI ERROR
SELECT nama_produk, DENSE_RANK() OVER (ORDER BY omzet DESC) AS rk
FROM produk
WHERE DENSE_RANK() OVER (ORDER BY omzet DESC) <= 3;
Alasannya: SQL ngerjain WHERE duluan, baru window function dihitung. Waktu WHERE jalan, nilai rk belum ada.
Solusinya bungkus pakai CTE atau subquery, terus filter di luar — persis kayak contoh top 3 per kota di atas.
Kesalahan umum
Pakai RANK buat top-N padahal maunya N tingkatan. Hasil "top 3" kamu bisa cuma keluar 2 tier harga kalau ada seri.
Lupa PARTITION BY waktu butuh per-grup. Tanpa PARTITION BY, nomor urutnya jalan terus dari baris pertama sampai terakhir tanpa reset.
Lupa ORDER BY di dalam OVER. RANK dan DENSE_RANK wajib punya ORDER BY di dalam OVER — kalau nggak, gak ada dasar buat ngurutin.
Nyoba di MySQL 5.7. Window function baru ada di MySQL 8.0. Kalau kamu masih di 5.7, query-nya bakal error syntax. Cek versinya pakai SELECT VERSION();.
FAQ
Apa bedanya RANK dan DENSE_RANK?
RANK ngeloncat setelah nilai kembar (1, 1, 3). DENSE_RANK lanjut rapat tanpa loncat (1, 1, 2). Kalau gak ada nilai kembar, hasilnya identik.
Kapan pakai ROW_NUMBER?
Waktu kamu butuh nomor unik tiap baris tanpa peduli ada yang seri — misalnya buat hapus duplikat atau ambil tepat satu transaksi terbaru per pelanggan.
Gimana cara ambil top 3 per kategori?
Bungkus DENSE_RANK plus PARTITION BY di dalam CTE, terus filter WHERE rk <= 3 di query luar. Window function gak bisa dipakai langsung di WHERE.
Jalan di MySQL dan PostgreSQL?
Jalan. PostgreSQL support sejak 8.4, MySQL sejak 8.0. Di MySQL 5.7 ke bawah gak ada window function — kamu harus akalin pakai variabel user atau self-join.
Penutup
Aturan sederhananya: RANK buat peringkat yang ngitung berapa banyak di atasnya, DENSE_RANK buat ngitung tingkatan nilai, ROW_NUMBER buat ambil tepat satu baris.
Dan window function selalu difilter dari luar, lewat CTE.
Definisi resmi ketiganya bisa kamu cek di dokumentasi PostgreSQL.
Kalau kamu belum kenal konsep window function-nya sendiri, mulai dari penjelasan window function di glossary.
Mau latihan query ranking pakai data UMKM Indonesia? Coba NgulikSQL — langsung nulis query di browser, hasilnya keluar detik itu juga.
Lanjut baca: Calculated Field Looker Studio: Bikin Metrik Sendiri.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel Terkait
Churn Rate SQL: Deteksi Pelanggan yang Kabur Sejak Dini
Studi kasus toko_berkah: 1.240 pelanggan hilang dalam setahun, dan sinyalnya udah kelihatan 2 bulan sebelumnya. Ini query yang bikin kamu nangkap mereka lebih awal.
YoY Growth SQL: Bandingkan Performa dengan Tahun Lalu
Hitung YoY growth di SQL pakai LAG atau self-join, lengkap dengan cara ngindarin pembagi nol dan jebakan bulan yang bolong. Contohnya pakai data toko_berkah.
MoM Growth SQL: Hitung Pertumbuhan Bulanan Otomatis
Berhenti copy-paste omzet bulan lalu ke Excel buat ngitung growth. Satu query SQL pakai LAG bisa ngasih pertumbuhan bulanan otomatis, lengkap sampai bulan yang datanya kosong.