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.
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.
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 tanggal di 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.
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.
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.
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.
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.
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.
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.
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.
Window function baru ada di MySQL 8.0. Di 5.7, query kamu bakal error syntax. Cek versinya pakai SELECT VERSION();.
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.
| 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.
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.
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.
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.
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.
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.
Yang perlu kamu inget:
SUM(x) OVER (ORDER BY tanggal): itu inti running total-nya.SUM(SUM(...)).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.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.