Running Total SQL: Hitung Akumulasi Penjualan per Hari
TL;DR
Running total SQL adalah akumulasi berjalan — total sampai baris ini, termasuk semua baris sebelumnya. Cara nulisnya pakai window function: SUM(omzet) OVER (ORDER BY tanggal). Klausa ORDER BY di dalam OVER yang nentuin urutan akumulasinya, dan kamu bisa pisahin per grup pakai PARTITION BY, misalnya per kota. Jebakan yang paling sering: tanggal yang nggak ada transaksinya bakal ilang dari hasil, jadi grafik akumulasinya keliatan bolong.
Running total SQL adalah akumulasi berjalan — total sampai baris ini, termasuk semua baris sebelumnya.
Omzet 1 April Rp 12 juta. 2 April Rp 15 juta. Running total di tanggal 2: Rp 27 juta.
Query-nya satu baris tambahan doang:
SUM(total) OVER (ORDER BY tanggal) AS running_total
Yang bikin banyak orang mundur bukan sintaksnya. Tapi bagian OVER yang kelihatan asing.
Apa itu running total di SQL?
Running total adalah kolom hasil hitungan yang nunjukin jumlah kumulatif dari baris pertama sampai baris yang lagi dibaca. Di SQL, ini dikerjain sama window function — fungsi yang ngitung agregat tapi tetap nyimpen semua baris aslinya, beda dari GROUP BY yang nge-collapse baris jadi satu.
Bedanya di sini:
| Fungsi | Input 30 baris | Output |
|---|---|---|
SUM(total) + GROUP BY | 30 transaksi | 1 baris total |
SUM(total) OVER (...) | 30 transaksi | 30 baris + kolom akumulasi |
Itu yang bikin window function berguna buat running total. Kamu masih lihat detail hariannya, plus akumulasinya di sebelah.
Gimana cara nulis running total SQL?
Sintaks dasarnya:
SELECT
tanggal,
total,
SUM(total) OVER (ORDER BY tanggal) AS running_total
FROM penjualan
ORDER BY tanggal;
Tiga bagian yang perlu kamu paham:
SUM(total)— apa yang mau diakumulasi.OVER— nandain ini window function, bukan agregasi biasa.ORDER BY tanggaldi dalam kurung — urutan akumulasinya. Ini yang paling penting.
ORDER BY di dalam OVER beda sama ORDER BY di akhir query. Yang di dalam nentuin urutan penjumlahan. Yang di luar nentuin urutan tampilan. Sering kebetulan sama, tapi konsepnya beda.
Running total per hari, bukan per transaksi
Query di atas ngakumulasi per transaksi. Kalau ada 400 transaksi di 1 April, kamu dapat 400 baris.
Yang kamu mau biasanya per hari. Berarti kamu perlu GROUP BY dulu, baru akumulasi:
SELECT
tanggal,
SUM(total) AS omzet_harian,
SUM(SUM(total)) OVER (ORDER BY tanggal) AS running_total
FROM penjualan
WHERE tanggal BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY tanggal
ORDER BY tanggal;
Iya, ada SUM(SUM(total)). Ini bukan typo.
SUM yang di dalam ngerjain GROUP BY — nghasilin omzet harian. SUM yang di luar (yang pakai OVER) ngakumulasi hasil itu.
SQL ngeksekusi GROUP BY dulu, baru window function. Makanya window function bisa "lihat" hasil agregasi.
Kalau kamu belum kenal GROUP BY, itu yang harus kamu kuasain duluan.
Contoh kasus: akumulasi omzet toko_berkah April 2026
Dataset toko_berkah — transaksi ritel UMKM dari 6 kota. Aku jalanin query di atas buat 10 hari pertama April:
| Tanggal | Omzet Harian | Running Total |
|---|---|---|
| 2026-04-01 | Rp 12.400.000 | Rp 12.400.000 |
| 2026-04-02 | Rp 14.800.000 | Rp 27.200.000 |
| 2026-04-03 | Rp 18.100.000 | Rp 45.300.000 |
| 2026-04-04 | Rp 24.600.000 | Rp 69.900.000 |
| 2026-04-05 | Rp 26.200.000 | Rp 96.100.000 |
| 2026-04-06 | Rp 11.900.000 | Rp 108.000.000 |
| 2026-04-07 | Rp 12.700.000 | Rp 120.700.000 |
| 2026-04-08 | Rp 13.100.000 | Rp 133.800.000 |
| 2026-04-09 | Rp 15.400.000 | Rp 149.200.000 |
| 2026-04-10 | Rp 22.900.000 | Rp 172.100.000 |
Ada pola yang cuma keliatan dari kolom akumulasi.
Tanggal 4 dan 5 (Sabtu-Minggu) nyumbang Rp 50,8 juta — 29% dari total 10 hari, cuma dari 2 hari. Weekday rata-rata Rp 13,4 juta per hari. Weekend Rp 25,4 juta.
Hampir 2x lipat.
Angka itu langsung ngubah keputusan: stok dikirim Jumat sore, bukan Senin pagi.
Running total per kota pakai PARTITION BY
Kalau kamu mau akumulasinya reset tiap ganti kota, tambahin PARTITION BY:
SELECT
kota,
tanggal,
SUM(total) AS omzet_harian,
SUM(SUM(total)) OVER (
PARTITION BY kota
ORDER BY tanggal
) AS running_total_kota
FROM penjualan
WHERE tanggal BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY kota, tanggal
ORDER BY kota, tanggal;
PARTITION BY kota mecah data jadi grup per kota. Akumulasinya jalan sendiri-sendiri di tiap grup, dan balik ke nol tiap ganti kota.
Ini yang paling sering dipakai buat bandingin kecepatan pertumbuhan antar wilayah dalam satu query.
Frame: ROWS BETWEEN
Ada bagian opsional yang jarang dipakai tapi penting waktu kepakai: frame.
SUM(total) OVER (
ORDER BY tanggal
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
Ini nentuin baris mana aja yang ikut dijumlahin. UNBOUNDED PRECEDING artinya dari baris paling awal, CURRENT ROW artinya sampai baris ini.
Kalau kamu nggak nulis frame-nya, SQL pakai default ini juga. Jadi buat running total biasa, kamu nggak perlu nulisnya.
Tapi frame jadi berguna kalau kamu mau moving average 7 hari:
AVG(SUM(total)) OVER (
ORDER BY tanggal
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rata_rata_7_hari
Ini yang ngeratain fluktuasi weekend-weekday tadi.
Kesalahan umum bikin running total
1. Tanggal bolong
Ini yang paling sering bikin grafik salah. Kalau tanggal 14 April nggak ada transaksi sama sekali, baris itu nggak muncul di hasil query.
SQL cuma balikin baris yang ada di tabel. Nggak ada transaksi = nggak ada baris.
Solusinya: bikin tabel kalender berisi semua tanggal, terus LEFT JOIN penjualan ke situ.
SELECT
k.tanggal,
COALESCE(SUM(p.total), 0) AS omzet_harian,
SUM(COALESCE(SUM(p.total), 0)) OVER (ORDER BY k.tanggal) AS running_total
FROM kalender k
LEFT JOIN penjualan p ON p.tanggal = k.tanggal
WHERE k.tanggal BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY k.tanggal
ORDER BY k.tanggal;
COALESCE ngubah NULL jadi 0, jadi akumulasinya tetap jalan mulus. Cara kerja COALESCE aku bahas terpisah.
2. Lupa ORDER BY di dalam OVER
Kalau kamu nulis SUM(total) OVER () tanpa ORDER BY, hasilnya bukan running total — tapi grand total yang sama di semua baris. Nggak error, tapi salah.
3. ORDER BY pakai kolom yang nggak unik
Kalau ada 2 baris dengan tanggal sama dan kamu ORDER BY tanggal doang, urutan di antara keduanya nggak dijamin. Buat running total per hari ini nggak masalah, tapi buat running total per transaksi, tambahin tiebreaker: ORDER BY tanggal, order_id.
4. Pakai MySQL 5.7
Window function baru ada di MySQL 8.0. Di 5.7, query kamu bakal error syntax. Cek versinya pakai SELECT VERSION();.
5. WHERE yang motong data yang kamu butuh
Kalau kamu mau running total dari awal tahun tapi nulis WHERE tanggal >= '2026-04-01', akumulasinya mulai dari nol di April. Kalau maunya lanjutan dari Januari, jangan filter di WHERE — filter hasilnya belakangan pakai subquery.
Kompatibilitas antar database
| Database | Dukungan window function |
|---|---|
| PostgreSQL | Sejak 8.4 (2009) |
| MySQL | Sejak 8.0 (2018) |
| SQL Server | Sejak 2012 |
| BigQuery | Sejak awal |
| SQLite | Sejak 3.25 (2018) |
Sintaksnya nyaris identik di semuanya. Referensi lengkapnya ada di dokumentasi resmi PostgreSQL soal window function.
FAQ
Apa bedanya SUM biasa sama SUM OVER?
SUM biasa nge-collapse banyak baris jadi satu angka. Kamu punya 30 baris transaksi, hasilnya 1 baris total. SUM OVER tetap nyimpen semua baris, tapi nambahin satu kolom hasil hitungan di tiap baris. Jadi kamu masih lihat detail per hari, plus akumulasinya di sebelahnya. Itu yang bikin window function cocok buat running total.
Kenapa running total-ku loncat di beberapa tanggal?
Karena tanggal yang nggak ada transaksinya nggak muncul di hasil query. SQL cuma balikin baris yang ada di tabel. Kalau tanggal 14 April nggak ada penjualan sama sekali, dia hilang dan grafik kamu jadi bolong. Solusinya: bikin tabel kalender berisi semua tanggal, lalu LEFT JOIN tabel penjualan ke situ sebelum hitung running total.
Running total bisa dipisah per kota?
Bisa, pakai PARTITION BY. Tulis SUM(total) OVER (PARTITION BY kota ORDER BY tanggal). Akumulasinya bakal reset ke nol tiap ganti kota, jadi kamu dapat running total Jakarta sendiri, Surabaya sendiri, dan seterusnya. Ini yang paling sering dipakai buat bandingin kecepatan pertumbuhan antar wilayah dalam satu query.
Kenapa harus ada SUM dua kali di query-ku?
Karena kamu lagi gabungin agregasi biasa sama window function. SUM(total) yang di dalam ngerjain GROUP BY per tanggal, ngasilin omzet harian. SUM(...) OVER yang di luar ngambil hasil itu lalu ngakumulasinya. Kalau kamu nulis SUM(total) OVER doang tanpa GROUP BY, hasilnya jadi akumulasi per transaksi, bukan per hari.
Window function jalan di MySQL versi lama?
Nggak. Window function baru masuk MySQL di versi 8.0, rilis 2018. Kalau kamu masih pakai MySQL 5.7, kamu harus pakai cara lama — self-join atau variabel session, dua-duanya lebih lambat dan lebih ribet. PostgreSQL udah dukung window function sejak versi 8.4, jadi hampir semua instalasi PostgreSQL yang aktif sekarang aman.
Penutup
Yang perlu kamu inget:
SUM(x) OVER (ORDER BY tanggal)— itu inti running total-nya.- Kalau mau per hari, GROUP BY dulu, dan siap-siap lihat
SUM(SUM(...)). - Tanggal bolong bikin grafik kamu bohong. LEFT JOIN ke tabel kalender.
Coba jalanin query pertamanya di data kamu sendiri hari ini. Sepuluh baris cukup buat lihat polanya.
Kalau OVER masih terasa asing, mulai dari definisi window function di glossary — sekali paham konsepnya, RANK dan LAG jadi gampang.
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.