Pernahkah kamu duduk di depan layar monitor sambil mengelus dada karena query SQL yang kamu tulis bentuknya makin mirip mi instan pilin-memilin, padahal tugas yang diminta atasan sangat sederhana: menampilkan 3 produk terlaris dari masing-masing kategori produk?
Jika kamu pernah berada di posisi itu—menulis self-join bertingkat-tingkat, menumpuk subquery yang membingungkan, hanya untuk memfilter peringkat—kamu tidak sendirian. Masalah “Top N per kelompok” adalah salah satu tembok besar yang paling sering membuat database developer, backend engineer, dan data analyst junior menggaruk kepala yang tidak gatal.
Good news-nya: SQL sebenarnya sudah punya fitur bawaan yang memang diciptakan khusus untuk menyelesaikan masalah ini secara bersih, elegan, dan jauh lebih cepat. Fitur sakti tersebut bernama Window Function.
Di artikel ini kita akan belajar cara mengubah query SQL tradisional yang rumit menjadi query Window Function yang bersih dan cepat, lengkap dengan praktik langsung menggunakan dataset transaksi yang bisa kamu coba sendiri di database kamu.
Skenario Nyata: Masalah “Top N per Kelompok”
Mari kita mulai dari skenario nyata yang sering kita temui di dunia kerja. Bayangkan kamu sedang mengelola database backend sebuah aplikasi e-commerce. Kamu memiliki tabel transaksi penjualan dan diminta oleh tim produk untuk menampilkan top 3 transaksi penjualan terbesar untuk setiap kategori barang.
Jika kamu hanya mengandalkan perintah dasar SQL yang diajarkan di kelas dasar—seperti GROUP BY dan ORDER BY—kamu akan langsung membentur tembok. Kenapa?
Sebab, fungsi utama GROUP BY adalah memadatkan (collapse) banyak baris menjadi satu baris rangkuman ringkas. Saat kamu mengelompokkan data berdasarkan kategori barang, kamu secara otomatis kehilangan akses ke baris detail transaksi individual di dalam kategori tersebut.
Cara Lama: Correlated Subquery
Sebelum fitur Window Function didukung secara luas di berbagai Sistem Manajemen Database (DBMS), berikut adalah salah satu bentuk query yang terpaksa ditulis oleh para developer menggunakan teknik correlated subquery:
SELECT p1.kategori, p1.nama_produk, p1.total_penjualan
FROM penjualan p1
WHERE (
SELECT COUNT(*)
FROM penjualan p2
WHERE p2.kategori = p1.kategori
AND p2.total_penjualan > p1.total_penjualan
) < 3
ORDER BY p1.kategori, p1.total_penjualan DESC;
Jika kita bedah dari kacamata engineering dan efisiensi sistem, metode tradisional di atas menyimpan setidaknya tiga masalah besar:
- Performa yang sangat buruk (skalabilitas rendah). Query di atas menggunakan teknik correlated subquery. Artinya, untuk setiap satu baris data yang ada di tabel
p1, mesin database harus mengeksekusi ulang seluruh subquery pada tabelp2. Jika tabelmu memiliki 100.000 baris transaksi, database akan melakukan komparasi berulang hingga ratusan ribu kali. Akibatnya, query menjadi sangat lambat dan membebani memori server hingga terjadi lonjakan penggunaan CPU. - Keterbacaan kode yang buruk (poor readability). Bagi anggota tim lain yang baru pertama kali membaca kode ini saat proses code review, logika
WHERE (SELECT COUNT(*)...) < 3sama sekali tidak intuitif. Butuh waktu ekstra bagi siapa pun untuk mencerna apa maksud sebenarnya dari pembandingan jumlah baris tersebut. - Penanganan nilai seri (tie handling) yang ringkih. Jika ada dua produk dalam satu kategori yang memiliki nilai penjualan identik, query tradisional di atas sering kali memberikan jumlah output yang salah, misalnya malah menampilkan 4 produk padahal batas syarat yang diminta hanya 3 produk.
GROUP BY vs Window Function
Sebelum masuk ke praktik, penting untuk benar-benar memahami perbedaan mendasar antara kedua konsep pengelompokan data ini.
Secara sederhana, Window Function adalah fitur dalam SQL yang memungkinkan kita melakukan kalkulasi agregasi atau pemeringkatan lintas sekumpulan baris tabel (a set of table rows), tanpa menghilangkan identitas dan detail dari baris aslinya.
Mari kita bandingkan perbedaannya secara langsung:
| Aspek | GROUP BY | Window Function |
|---|---|---|
| Perlakuan baris | Menggabungkan (collapse) baris-baris data | Mempertahankan seluruh baris data |
| Hasil dari 10 baris input | Memadat menjadi 1 baris rangkuman | Tetap 10 baris, ditambah kolom hasil kalkulasi |
Nama “window” sendiri berasal dari konsep “jendela” atau kumpulan baris (frame) tertentu yang ditentukan oleh sistem untuk dihitung pada saat baris data aktif sedang diproses.
Anatomi Window Function
Tidak perlu bingung saat menulis Window Function. Ada rumus baku yang bisa langsung kamu terapkan setiap kali membuat query pemeringkatan:
[FUNGSI_WINDOW()] OVER (PARTITION BY [kolom_kelompok] ORDER BY [kolom_patokan] DESC)
Contoh penerapannya di lapangan:
DENSE_RANK() OVER (PARTITION BY kategori ORDER BY total_penjualan DESC)→ Menghasilkan nomor urut peringkat penjualan tanpa melompati angka jika ada nilai omzet yang seri.ROW_NUMBER() OVER (PARTITION BY kategori ORDER BY tanggal_transaksi DESC)→ Menghasilkan nomor baris sekuensial unik untuk setiap transaksi di tiap kategori.
Tiga Fungsi Pemeringkatan Utama
Untuk menyelesaikan kasus pemeringkatan seperti “Top N”, ada tiga fungsi pemeringkatan utama yang wajib kamu pahami bedanya:
ROW_NUMBER()— Memberikan nomor urut unik secara sekuensial (1, 2, 3, 4, dst.) untuk setiap baris data di dalam kelompoknya. Jika ada dua baris bernilai persis sama,ROW_NUMBER()tetap memberi urutan berbeda.RANK()— Memberikan nilai peringkat yang sama jika ditemukan nilai data yang setara, namun melompati urutan nomor berikutnya (contoh: 1, 2, 2, 4).DENSE_RANK()— Memberikan nilai peringkat yang sama untuk nilai data yang setara tanpa melompati nomor urut berikutnya (contoh: 1, 2, 2, 3). Ini adalah fungsi terbaik dan paling aman untuk kasus Top 3!
Praktik Langsung: Siapkan Dataset Kamu Sendiri
Supaya latihan ini terasa nyata, jangan langsung copy-paste data orang lain—buat dataset versi kamu sendiri. Bukalah query editor pilihanmu (DBeaver, pgAdmin, atau MySQL Workbench), lalu buat tabel dengan struktur berikut:
CREATE TABLE penjualan_elektronik (
id INT PRIMARY KEY,
nama_produk VARCHAR(100),
kategori VARCHAR(50),
total_penjualan BIGINT
);
Setelah tabel jadi, isi sendiri datanya dengan INSERT INTO. Supaya hasil “Top 3 per kategori” nanti benar-benar kelihatan bedanya, ikuti panduan berikut saat mengisi data:
- Buat minimal 3 kategori berbeda (misalnya kategori produk yang biasa kamu jual/temui sehari-hari).
- Isi minimal 4–5 baris produk untuk tiap kategori, supaya ada produk yang “tersisih” dari Top 3.
- Sengaja buat dua baris dengan
total_penjualanyang sama persis di salah satu kategori—ini penting supaya kamu bisa lihat sendiri bedanyaRANK(),DENSE_RANK(), danROW_NUMBER()saat terjadi nilai seri (tie).
Contoh format INSERT yang bisa kamu sesuaikan:
INSERT INTO penjualan_elektronik (id, nama_produk, kategori, total_penjualan) VALUES
(1, 'Nama Produk 1', 'Kategori A', 0),
(2, 'Nama Produk 2', 'Kategori A', 0),
(3, 'Nama Produk 3', 'Kategori B', 0);
-- lanjutkan sendiri sampai semua kategori terisi

Setelah data kamu sendiri siap, lanjutkan ke query-query berikut menggunakan tabel penjualan_elektronik versi kamu.
Mau Kuasai Kenapa Query SQL Kamu Ribet Banget Cuma untuk Cari “Top 3 per Kategori”? Kenalan dengan Window Function Sampai Bisa Praktik Langsung?
Materi ini juga kami ajarkan langsung di kelas Edusoft Center, dibimbing mentor, sampai kamu bisa praktik nyata — bukan cuma baca teori.
Tanya Kursus via WhatsApp Lihat contoh project nyata dari siswa kami →
Menyelesaikan Kasus Top 3 dengan CTE + DENSE_RANK()
Sekarang mari kita selesaikan tantangan utama kita menggunakan kombinasi CTE (Common Table Expression) dan Window Function DENSE_RANK().
WITH PeringkatPenjualan AS (
SELECT
kategori,
nama_produk,
total_penjualan,
DENSE_RANK() OVER (
PARTITION BY kategori
ORDER BY total_penjualan DESC
) AS rangking
FROM penjualan_elektronik
)
SELECT
kategori,
rangking,
nama_produk,
total_penjualan
FROM PeringkatPenjualan
WHERE rangking <= 3
ORDER BY kategori, rangking;

Query di atas bekerja dalam lima langkah intuitif di belakang layar:
PARTITION BYmengelompokkan baris berdasarkan kategori.ORDER BYmengurutkan omzet penjualan secara menurun.DENSE_RANKmenghitung angka peringkat tiap baris.- CTE menyimpan tabel abstrak sementara bernama
PeringkatPenjualan. WHERE rangking <= 3menyaring hanya peringkat 1 sampai 3 dari tiap kelompok.
Membandingkan ROW_NUMBER, RANK, dan DENSE_RANK
Untuk membuktikan perbedaan nyata saat ada nilai penjualan yang persis sama (seperti Laptop Office Z dan Laptop Budget A yang sama-sama beromzet 90 juta), mari jalankan ketiga fungsi pemeringkatan tersebut sekaligus dalam satu query:
SELECT
kategori,
nama_produk,
total_penjualan,
ROW_NUMBER() OVER (PARTITION BY kategori ORDER BY total_penjualan DESC) AS hasil_row_number,
RANK() OVER (PARTITION BY kategori ORDER BY total_penjualan DESC) AS hasil_rank,
DENSE_RANK() OVER (PARTITION BY kategori ORDER BY total_penjualan DESC) AS hasil_dense_rank
FROM penjualan_elektronik;

- Langsung menggunakan klausa
WHEREpada Window Function di query utama (Window Function harus dieksekusi di dalam CTE atau subquery terlebih dahulu). - Salah memilih antara
RANK()danDENSE_RANK()saat aturan bisnis membutuhkan urutan peringkat yang tidak boleh terputus atau melompat. - Lupa mencantumkan klausa
ORDER BYdi dalamOVER(), yang menyebabkan semua baris dalam kelompok mendapatkan nomor peringkat 1. - Tetap menggunakan correlated subquery lama untuk dataset berukuran jutaan baris di lingkungan produksi.
Lebih dari Sekadar Pemeringkatan
Hebatnya lagi, Window Function tidak terbatas pada pemeringkatan. Kamu bisa menggabungkannya dengan fungsi agregasi seperti SUM() untuk menghitung persentase kontribusi produk terhadap total kategori tanpa perlu melakukan query terpisah atau join tambahan.
SELECT
kategori,
nama_produk,
total_penjualan,
SUM(total_penjualan) OVER (PARTITION BY kategori) AS total_penjualan_kategori,
ROUND(
(total_penjualan::NUMERIC / SUM(total_penjualan) OVER (PARTITION BY kategori)) * 100,
2
) AS persentase_kontribusi
FROM penjualan_elektronik
ORDER BY kategori, total_penjualan DESC;

Selain pemeringkatan dan agregasi dinamis, Window Function juga memiliki fungsi navigasi seperti LAG() dan LEAD() untuk mengintip nilai pada baris sebelum atau sesudahnya secara instan tanpa perlu melakukan self-join yang lambat.
SELECT
kategori,
nama_produk,
total_penjualan,
LAG(total_penjualan, 1) OVER (PARTITION BY kategori ORDER BY total_penjualan DESC) AS penjualan_produk_diatasnya,
total_penjualan - LAG(total_penjualan, 1) OVER (PARTITION BY kategori ORDER BY total_penjualan DESC) AS selisih_dengan_posisi_atas
FROM penjualan_elektronik;

Kenapa Ini Penting dari Sisi Performa
Jika kita melihat dari kacamata mesin database (query planner dan execution plan), penggunaan Window Function menawarkan keuntungan arsitektural yang sangat besar dibanding cara tradisional.
Pada metode subquery tradisional, mesin database terpaksa melakukan pembacaan tabel (full table scan atau index scan) secara berulang-ulang sebanyak jumlah baris data yang diproses. Jika terdapat 100.000 baris data, mesin akan membaca ulang tabel ratusan ribu kali, yang berujung pada tingginya angka I/O disk dan lonjakan penggunaan CPU server.
Sebaliknya, saat menggunakan Window Function, mesin database modern (seperti PostgreSQL, MySQL 8.0, Oracle, atau SQL Server) dapat mengoptimalkan eksekusi data hanya dengan satu kali pembacaan tabel (single pass scan) ditambah proses pengurutan lokal di memori (sort/hash step). Ini membuat proses eksekusi puluhan kali lebih cepat dan jauh lebih hemat sumber daya server.
Mungkin terdengar sederhana untuk hanya memikirkan query yang penting bisa jalan. Tapi di dunia kerja nyata, eksekusi query pada database berskala besar sering kali menjadi penentu utama responsivitas aplikasi backend maupun kecepatan loading dashboard analytics perusahaan.
Kalau kamu masih menggunakan correlated subquery tradisional, server database tempat aplikasi berjalan akan bekerja berulang kali untuk memproses baris yang sama. Dan ketika server harus bekerja terlalu keras, sistem cenderung lambat atau bahkan mengalami downtime saat diakses oleh banyak pengguna secara bersamaan.
Sebaliknya, ketika kamu menggunakan Window Function, database engine dapat melakukan optimasi eksekusi dalam waktu hitungan milidetik. Rekan tim backend, data engineer, dan atasan bisnis bisa langsung menikmati laporan data yang cepat, akurat, dan efisien.
Inilah yang membedakan seorang database developer yang biasa saja dengan yang profesional: bukan hanya soal membuat query yang bisa menghasilkan keluaran data, tapi membuat query yang efisien, berstruktur rapi, mudah dirawat, dan siap dipakai di tingkat produksi.
✅ Checklist Sebelum Finalisasi Query
Sebelum query SQL kamu dianggap selesai dan siap di-push ke repository atau diterapkan pada server produksi, coba jawab lima pertanyaan penting ini:
- Apakah kamu sudah menggunakan CTE (
WITH ... AS) agar struktur query mudah dibaca oleh tim lain? - Apakah fungsi pemeringkatan yang dipilih (
ROW_NUMBER,RANK, atauDENSE_RANK) sudah sesuai dengan aturan bisnis? - Apakah klausa
PARTITION BYdanORDER BYdi dalamOVER()sudah terpasang dengan benar dan tidak tertukar? - Apakah filter batas peringkat sudah diletakkan pada outer query setelah pembuatan CTE?
- Apakah hasil akhir query sudah diuji pada kasus data yang memiliki nilai omzet kembar (tie)?
Penutup
Penulisan query SQL yang efisien bukan sekadar formalitas teknis. Ia adalah jembatan utama antara data mentah yang tersimpan di database dengan keputusan bisnis yang diambil oleh pemangku kepentingan perusahaan. Menguasai Window Function memberi kamu kemampuan menulis kode yang bersih, cepat, dan berstandar industri modern.
Mulai dari sekarang, setiap kali diminta membuat laporan Top N per kelompok atau pemeringkatan data, tinggalkan pola correlated subquery lama dan beralihlah menggunakan Window Function bersama CTE.
Satu perubahan kebiasaan kecil itu adalah investasi besar yang membuat seluruh hasil kerja analisis dan backend engineering kamu benar-benar berkualitas tinggi serta dihargai di lingkungan profesional.
Penulis: Sandya Gifta Ulimaz Sofiamagaski
Mau Kuasai Kenapa Query SQL Kamu Ribet Banget Cuma untuk Cari “Top 3 per Kategori”? Kenalan dengan Window Function Sampai Bisa Praktik Langsung?
Materi ini juga kami ajarkan langsung di kelas Edusoft Center, dibimbing mentor, sampai kamu bisa praktik nyata — bukan cuma baca teori.
Tanya Kursus via WhatsApp Lihat contoh project nyata dari siswa kami →