Window Function di SQL
ROW_NUMBER, RANK, LAG/LEAD, dan running total dengan SUM OVER: agregasi tanpa meruntuhkan baris, plus peran penting PARTITION BY.
Kenapa window function terasa seperti sihir
GROUP BY punya satu keterbatasan besar: ia meruntuhkan banyak baris menjadi satu baris ringkasan. Bagaimana kalau kamu ingin keduanya, detail tiap baris plus angka ringkasannya? Contoh: tampilkan tiap transaksi beserta total omzet hari itu, atau beri peringkat tiap produk tanpa menghilangkan daftar produknya. Window function menjawab ini. Ia menghitung agregasi "di jendela" baris-baris terkait, lalu menempelkan hasilnya ke tiap baris asli. Baris tidak hilang, informasi bertambah.
Analogi: window function itu nilai rapor plus peringkat kelas
Bayangkan rapor sekolah. Di samping nilaimu, tertulis peringkatmu di kelas dan rata-rata kelas. Nilai tiap siswa tetap ada satu per satu (tidak diringkas jadi satu baris "kelas"), tapi tiap baris diperkaya dengan konteks kelompoknya. PARTITION BY adalah "kelasnya": ia menentukan kelompok mana yang jadi pembanding untuk tiap baris.
Contoh 1: ROW_NUMBER dan RANK untuk peringkat produk
SELECT
nama_produk,
kategori,
total_terjual,
ROW_NUMBER() OVER (ORDER BY total_terjual DESC) AS peringkat_global,
RANK() OVER (PARTITION BY kategori ORDER BY total_terjual DESC) AS peringkat_kategori
FROM produk;Dua fungsi, dua wawasan. ROW_NUMBER memberi nomor urut 1, 2, 3 tanpa peduli nilai kembar. RANK memberi peringkat dengan melompati angka kalau ada nilai sama (dua produk peringkat 1, berikutnya peringkat 3). Dan lihat PARTITION BY kategori: peringkat dihitung ulang dari 1 untuk tiap kategori, sehingga kamu dapat "juara per kategori", bukan cuma juara umum.
Contoh 2: LAG dan LEAD untuk perbandingan antar baris
SELECT
tanggal,
omzet_harian,
LAG(omzet_harian) OVER (ORDER BY tanggal) AS omzet_kemarin,
omzet_harian - LAG(omzet_harian) OVER (ORDER BY tanggal) AS selisih
FROM rekap_harian
ORDER BY tanggal;LAG mengambil nilai dari baris sebelumnya (kemarin), LEAD dari baris sesudahnya (besok). Pola ini adalah fondasi semua analisis tren di SQL: pertumbuhan harian, selisih minggu ke minggu, deteksi lonjakan. Tanpa LAG, kamu harus join tabel ke dirinya sendiri dengan kondisi tanggal, jauh lebih ribet dan lambat.
Contoh 3: running total dengan SUM OVER
SELECT
tanggal,
omzet_harian,
SUM(omzet_harian) OVER (ORDER BY tanggal) AS omzet_kumulatif,
SUM(omzet_harian) OVER (
PARTITION BY kategori ORDER BY tanggal
) AS kumulatif_per_kategori
FROM rekap_harian
ORDER BY kategori, tanggal;SUM(...) OVER (ORDER BY tanggal) menjumlahkan dari baris pertama sampai baris saat ini: running total. Tambahkan PARTITION BY dan running total dihitung ulang per kategori. Chart "omzet kumulatif" yang sering diminta manajer pada dasarnya adalah query ini divisualisasikan.
Contoh 4: gabungan realistis, top 3 produk per kategori
WITH peringkat AS (
SELECT
kategori,
nama_produk,
SUM(total_bayar) AS omzet,
RANK() OVER (
PARTITION BY kategori ORDER BY SUM(total_bayar) DESC
) AS rnk
FROM penjualan
JOIN produk USING (id_produk)
WHERE tanggal >= '2026-10-01'
GROUP BY kategori, nama_produk
)
SELECT kategori, nama_produk, omzet
FROM peringkat
WHERE rnk <= 3
ORDER BY kategori, rnk;Ini pola "top N per grup" yang sangat sering muncul di interview data analyst. Window function tidak bisa dipakai langsung di WHERE (karena dievaluasi setelah WHERE), jadi dibungkus CTE dulu, baru disaring rnk <= 3. Kombinasi CTE plus window function seperti ini adalah tanda kamu sudah di level mahir.
Jebakan umum 1: lupa PARTITION BY
SALAH: menulis RANK() OVER (ORDER BY total_terjual DESC) saat yang kamu mau adalah peringkat per kategori. Hasilnya peringkat global, dan "juara kategori" yang kamu laporkan sebenarnya salah.
BENAR: selalu tanyakan "peringkat/agregasi ini dihitung dalam kelompok apa?" sebelum menulis OVER. Kalau jawabannya "per kategori", "per bulan", atau "per kasir", maka PARTITION BY wajib ada. Lupa PARTITION BY adalah bug tersenyap di SQL karena query-nya tetap jalan tanpa error.
Jebakan umum 2: window function dipakai di WHERE atau GROUP BY
SALAH: WHERE RANK() OVER (...) <= 3. SQL menolak karena urutan eksekusi: WHERE berjalan sebelum window function dihitung. BENAR: bungkus dulu dengan CTE atau subquery (seperti contoh 4), lalu saring di query luar. Aturan yang sama berlaku untuk alias window function di GROUP BY.
Catatan teknis: Klausa frame (
ROWS BETWEEN) memberi kontrol presisi atas "jendela"-nya, misal rata-rata bergerak 7 hari:AVG(omzet) OVER (ORDER BY tanggal ROWS BETWEEN 6 PRECEDING AND CURRENT ROW). Default frame saat ada ORDER BY adalah dari awal partisi sampai baris saat ini, yang menjelaskan kenapa SUM OVER langsung menjadi running total.
Ringkasan cepat
- Window function: agregasi tanpa meruntuhkan baris asli.
- ROW_NUMBER untuk nomor urut, RANK untuk peringkat (lompat saat seri), LAG/LEAD untuk bandingkan antar baris, SUM OVER untuk running total.
- PARTITION BY menentukan kelompok pembanding, lupa memakainya adalah bug tersenyap.
- Window function tidak bisa di WHERE, bungkus dengan CTE dulu.
Tantangan
Peringkat dan tren penjualan
Dari tabel penjualan harian per produk (kolom: tanggal, kategori, nama_produk, omzet): tulis satu query yang menampilkan tiap baris plus (a) peringkat omzet produk dalam kategorinya, (b) selisih omzet vs hari sebelumnya per produk, (c) omzet kumulatif per kategori. Saring hanya 5 peringkat teratas tiap kategori.
Kuis Bab
Uji pemahamanmu: Bab 8: SQL untuk Data Analyst
Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".
1.Apa fungsi SELECT DISTINCT kota FROM pelanggan?
2.Apa beda WHERE dan HAVING?
3.Apa hasil LEFT JOIN antara tabel pelanggan dan tabel pesanan?
4.Apa beda COUNT(*) dan COUNT(email)?
5.Untuk apa ROW_NUMBER() OVER (PARTITION BY kota ORDER BY gaji DESC) dipakai?