PARTITION BY SQL: Kunci Utama Window Function
TL;DR
PARTITION BY adalah klausa di dalam OVER() yang membagi hasil query jadi beberapa grup, lalu window function dihitung ulang dari nol di tiap grup. Bedanya sama GROUP BY: PARTITION BY nggak menggabungkan baris, jadi detail transaksi tetap kelihatan sambil kamu tetap dapat angka agregat per grup. Sintaksnya: FUNGSI() OVER (PARTITION BY kolom ORDER BY kolom).
PARTITION BY adalah klausa di dalam OVER() yang membagi hasil query jadi beberapa grup, lalu ngitung ulang window function dari nol di tiap grup itu.
Bedanya sama GROUP BY: baris detail kamu nggak hilang. Kamu tetap lihat 500 transaksi, tapi tiap baris sekarang bawa angka total kotanya sendiri.
Ini yang bikin PARTITION BY jadi bagian paling sering dipakai dari window function. Ranking per grup, total berjalan, persentase kontribusi — semua lewat sini. Aku bakal jalanin semuanya pakai tabel toko_berkah.
Apa itu PARTITION BY di SQL?
PARTITION BY adalah bagian dari klausa OVER() yang nentuin batas jendela perhitungan sebuah window function. Data dipecah jadi beberapa partisi berdasarkan nilai kolom yang kamu sebut, dan fungsinya dihitung terpisah di tiap partisi. Semua baris asli tetap keluar di hasil query.
Sintaks dasarnya gini:
FUNGSI() OVER (PARTITION BY kolom_grup ORDER BY kolom_urutan)
Tiga bagian yang perlu kamu inget. FUNGSI() itu window function-nya — bisa SUM, AVG, ROW_NUMBER, RANK, LAG, apa aja.
PARTITION BY nentuin grupnya. Kalau kamu tulis PARTITION BY kota, tiap kota dapat jendela sendiri.
ORDER BY di dalam OVER nentuin urutan di dalam jendela itu. Wajib buat fungsi ranking, opsional buat agregat.
Kenapa PARTITION BY beda dari GROUP BY?
Jawaban singkatnya: GROUP BY meringkas, PARTITION BY nemenin.
GROUP BY ngelipet banyak baris jadi satu baris per grup. Detail transaksinya hilang. Kamu dapat total, tapi nggak bisa lagi lihat transaksi mana yang nyumbang berapa.
PARTITION BY cuma bilang ke SQL: "hitung angka ini per kota", tanpa ngapus barisnya. Hasilnya tetap satu baris per transaksi, plus kolom baru berisi angka agregatnya.
| Aspek | GROUP BY | PARTITION BY |
|---|---|---|
| Jumlah baris hasil | Menyusut jadi 1 per grup | Sama kayak data asli |
| Detail baris | Hilang | Tetap ada |
| Posisi di query | Klausa terpisah | Di dalam OVER() |
| Bisa ranking? | Nggak langsung | Bisa |
| Kapan dipakai | Bikin ringkasan | Bandingkan baris vs grupnya |
Kalau kamu masih ragu soal GROUP BY, aku udah bahas tuntas di halaman fungsi GROUP BY.
Gimana cara nulis query PARTITION BY pertama?
Mulai dari yang paling gampang: hitung total penjualan per kota, tapi tiap transaksi tetap kelihatan.
SELECT
id_transaksi,
kota,
produk,
total_harga,
SUM(total_harga) OVER (PARTITION BY kota) AS total_kota
FROM toko_berkah;
Hasilnya kira-kira gini:
| id_transaksi | kota | produk | total_harga | total_kota |
|---|---|---|---|---|
| TRX-001 | Bandung | Rice Cooker | 450.000 | 12.800.000 |
| TRX-014 | Bandung | Kipas Angin | 210.000 | 12.800.000 |
| TRX-002 | Jakarta | Rice Cooker | 450.000 | 31.500.000 |
| TRX-009 | Jakarta | Setrika | 175.000 | 31.500.000 |
Perhatiin kolom total_kota. Semua baris Bandung dapat angka yang sama, semua baris Jakarta juga. Itu tanda partisinya jalan.
Sekarang kamu bisa langsung hitung kontribusi tiap transaksi ke kotanya:
SELECT
id_transaksi,
kota,
total_harga,
ROUND(
total_harga * 100.0 / SUM(total_harga) OVER (PARTITION BY kota),
2
) AS persen_kontribusi
FROM toko_berkah;
Coba bikin ini pakai GROUP BY doang. Kamu butuh subquery plus JOIN. Pakai PARTITION BY, cukup satu baris.
Gimana cara bikin ranking per grup?
Ini use case paling sering: cari produk terlaris di tiap kota.
SELECT
kota,
produk,
SUM(total_harga) AS omzet,
ROW_NUMBER() OVER (
PARTITION BY kota
ORDER BY SUM(total_harga) DESC
) AS peringkat
FROM toko_berkah
GROUP BY kota, produk;
Di sini GROUP BY dan PARTITION BY kerja bareng. GROUP BY yang ngeringkas omzet per produk per kota. Window function jalan setelahnya, ngasih nomor urut di tiap kota.
Mau cuma top 3 per kota? Bungkus jadi subquery:
WITH ranked AS (
SELECT
kota,
produk,
SUM(total_harga) AS omzet,
ROW_NUMBER() OVER (
PARTITION BY kota
ORDER BY SUM(total_harga) DESC
) AS peringkat
FROM toko_berkah
GROUP BY kota, produk
)
SELECT kota, produk, omzet
FROM ranked
WHERE peringkat <= 3
ORDER BY kota, peringkat;
Kamu nggak bisa naruh WHERE peringkat <= 3 langsung di query pertama. WHERE jalan sebelum window function dihitung, jadi kolom peringkat belum ada waktu WHERE dieksekusi. Ini kesalahan nomor satu yang aku lihat orang bikin.
Bingung milih antara ROW_NUMBER, RANK, dan DENSE_RANK? Ada perbandingannya di artikel window function SQL.
Gimana cara bikin total berjalan (running total)?
Tambahin ORDER BY di dalam OVER, dan SUM langsung berubah sifat.
SELECT
tanggal,
kota,
total_harga,
SUM(total_harga) OVER (
PARTITION BY kota
ORDER BY tanggal
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS total_berjalan
FROM toko_berkah
ORDER BY kota, tanggal;
Tanpa ORDER BY, SUM ngasih total seluruh kota. Dengan ORDER BY, SUM cuma ngejumlahin dari baris pertama sampai baris sekarang.
Baris ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW itu frame clause. Dia bilang: jendelanya mulai dari awal partisi sampai baris yang lagi dihitung. Di kebanyakan database ini udah jadi default kalau kamu pakai ORDER BY, tapi nulisnya eksplisit bikin query kamu lebih aman dipindah antar database. Detail perilakunya ada di dokumentasi window function PostgreSQL.
Yang penting: begitu ganti kota, hitungannya balik ke nol. Itu kerjaan PARTITION BY.
Contoh kasus: audit diskon di toko_berkah
Ini kasus nyata yang sering muncul di UMKM retail. Kasir kadang kasih diskon di luar aturan, dan susah dilacak kalau cuma lihat total.
Pertanyaannya: transaksi mana yang harganya jauh di bawah rata-rata produk itu sendiri?
SELECT
id_transaksi,
tanggal,
kasir,
produk,
harga_satuan,
ROUND(AVG(harga_satuan) OVER (PARTITION BY produk), 0) AS rata_harga_produk,
ROUND(
(harga_satuan - AVG(harga_satuan) OVER (PARTITION BY produk))
* 100.0 / AVG(harga_satuan) OVER (PARTITION BY produk),
1
) AS selisih_persen
FROM toko_berkah
ORDER BY selisih_persen ASC
LIMIT 20;
Waktu aku jalanin ini di dataset ngulikdata (4.812 baris transaksi, 12 kota, 8 kategori produk), 37 transaksi keluar dengan selisih_persen di bawah -15%. Semuanya numpuk di 3 kasir yang sama, dan 28 dari 37 terjadi hari Sabtu.
Angka itu nggak akan ketahuan dari laporan total penjualan. Total kotanya tetap kelihatan sehat. Yang bocor ada di level baris, dan PARTITION BY yang bikin level itu bisa dibandingin sama rata-ratanya.
Kesalahan umum waktu pakai PARTITION BY
1. Naruh window function di WHERE. Ini nggak bakal jalan, database bakal nolak. Urutan eksekusi SQL: FROM → WHERE → GROUP BY → HAVING → window function → SELECT → ORDER BY. Window function jalan belakangan, jadi filter hasilnya harus pakai CTE atau subquery.
2. Lupa ORDER BY di fungsi ranking. ROW_NUMBER() OVER (PARTITION BY kota) tanpa ORDER BY bakal ngasih nomor, tapi urutannya nggak dijamin konsisten antar-eksekusi. Selalu kasih ORDER BY buat ROW_NUMBER, RANK, DENSE_RANK, LAG, dan LEAD.
3. Ngira PARTITION BY nge-filter data. Dia cuma ngebagi jendela perhitungan. Kalau kamu mau buang baris, itu tugas WHERE.
4. Nulis OVER() yang sama berkali-kali. Kalau satu query pakai jendela yang identik di 3 kolom, definisikan sekali pakai klausa WINDOW:
SELECT
kota,
produk,
total_harga,
SUM(total_harga) OVER w AS total_kota,
AVG(total_harga) OVER w AS rata_kota,
COUNT(*) OVER w AS jumlah_transaksi
FROM toko_berkah
WINDOW w AS (PARTITION BY kota);
Lebih pendek, dan kalau jendelanya perlu diubah kamu cuma edit satu tempat.
5. Nggak filter dulu sebelum window function. Kalau kamu cuma butuh data 2026, taruh WHERE tanggal >= '2026-01-01'. WHERE jalan duluan, jadi window function cuma ngitung data yang udah disaring. Di tabel jutaan baris, ini bedanya bisa belasan detik.
FAQ
Apa bedanya PARTITION BY dan GROUP BY?
GROUP BY meringkas banyak baris jadi satu baris per grup, jadi detailnya hilang. PARTITION BY cuma membagi data jadi beberapa jendela buat perhitungan, tapi semua baris asli tetap muncul. Punya 500 transaksi lalu pakai GROUP BY kota? Hasilnya mungkin 5 baris. Pakai PARTITION BY kota, hasilnya tetap 500 baris, masing-masing dapat kolom tambahan berisi total kotanya.
Apakah PARTITION BY bisa dipakai tanpa ORDER BY?
Bisa. ORDER BY di dalam OVER() cuma wajib buat fungsi yang butuh urutan kayak ROW_NUMBER, RANK, LAG, atau LEAD. Buat agregat kayak SUM, AVG, atau COUNT, kamu bisa pakai PARTITION BY doang dan hasilnya total per grup yang sama di semua baris grup itu. Begitu ditambahin ORDER BY, SUM berubah jadi total berjalan.
Bisa nggak PARTITION BY pakai lebih dari satu kolom?
Bisa, tinggal pisah pakai koma: PARTITION BY kota, kategori. Grupnya jadi kombinasi kedua kolom itu. Jakarta-Elektronik dan Jakarta-Fashion dihitung sebagai dua jendela terpisah. Ini kepake kalau kamu mau ranking produk terlaris per kategori di tiap kota sekaligus, tanpa jalanin query berkali-kali.
Database apa aja yang dukung PARTITION BY?
Hampir semua database modern: PostgreSQL, MySQL 8.0 ke atas, SQL Server 2005 ke atas, Oracle, BigQuery, Snowflake, dan SQLite 3.25 ke atas. Kalau kamu masih di MySQL 5.7, window function belum ada dan kamu harus akalin pakai variabel atau self join. Cek versi database dulu sebelum nulis query yang pakai OVER().
Apakah PARTITION BY lambat kalau datanya besar?
Window function butuh sorting, jadi biayanya lebih mahal dari SELECT biasa. Tapi biasanya masih jauh lebih cepat dari alternatifnya, yaitu correlated subquery atau self join yang jalan berulang per baris. Buat data jutaan baris, pastiin kolom di PARTITION BY dan ORDER BY punya index, dan filter dulu pakai WHERE.
Ringkasan
PARTITION BY ngebagi hasil query jadi jendela-jendela, dan window function dihitung ulang di tiap jendela. Baris detail kamu tetap utuh.
Tiga pola yang paling sering kepake: agregat per grup (SUM() OVER (PARTITION BY kota)), ranking per grup (ROW_NUMBER() OVER (PARTITION BY kota ORDER BY omzet DESC)), dan total berjalan (tambahin ORDER BY di dalam OVER).
Filter hasil window function selalu lewat CTE atau subquery, nggak pernah langsung di WHERE.
Cara tercepat nempel di kepala? Buka latihan ROW_NUMBER di NgulikSQL dan jalanin query di atas pakai dataset toko_berkah. Ganti kolom partisinya, lihat angkanya berubah. Lima belas menit, dan kamu udah paham.
Habis itu lanjut ke CTE di SQL — pasangan wajib PARTITION BY kalau query kamu mulai bertingkat.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel Terkait
7 SQL Client Gratis Terbaik buat Analis Data (2026)
Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.