Deret penjualan harian hampir tidak pernah mulus — faktor cuaca, hari libur, dan promosi membuat grafiknya zigzag, dan tren yang sesungguhnya tersembunyi di balik lonjakan-lonjakan kecil. Analis mengatasinya dengan rata-rata bergerak (moving average): untuk setiap hari, hitung rata-rata beberapa hari terakhir sehingga fluktuasi acak saling meniadakan dan arah tren menonjol. Window function menjalankan resep ini dalam satu kolom: AVG() dipadukan frame ROWS BETWEEN 2 PRECEDING AND CURRENT ROW menggeser jendela tiga baris sepanjang deret. Pada chapter ini kita membangun rata-rata bergerak tiga periode, membaca perilakunya di tepi deret, memasang jendela bergerak per kota, lalu membandingkan lebar jendela dua dan tiga periode pada data yang sama.
Menyiapkan Tabel Latihan #
Kita pakai database latihan belajar_window dengan tabel penjualan lima belas baris: lima hari berturut-turut untuk tiap kota, satu produk andalan per kota:
DROP DATABASE IF EXISTS belajar_window;
CREATE DATABASE belajar_window;
USE belajar_window;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE penjualan (
id INT AUTO_INCREMENT PRIMARY KEY,
tanggal DATE NOT NULL,
kota VARCHAR(30) NOT NULL,
produk VARCHAR(50) NOT NULL,
jumlah INT NOT NULL,
harga DECIMAL(10,2) NOT NULL
);
INSERT INTO penjualan (tanggal, kota, produk, jumlah, harga) VALUES
('2026-06-01', 'Jakarta', 'Kopi Gayo', 10, 55000.00),
('2026-06-01', 'Bandung', 'Teh Melati', 6, 25000.00),
('2026-06-01', 'Surabaya', 'Kopi Toraja', 15, 62000.00),
('2026-06-02', 'Jakarta', 'Kopi Gayo', 12, 55000.00),
('2026-06-02', 'Bandung', 'Teh Melati', 8, 25000.00),
('2026-06-02', 'Surabaya', 'Kopi Toraja', 13, 62000.00),
('2026-06-03', 'Jakarta', 'Kopi Gayo', 9, 55000.00),
('2026-06-03', 'Bandung', 'Teh Melati', 7, 25000.00),
('2026-06-03', 'Surabaya', 'Kopi Toraja', 18, 62000.00),
('2026-06-04', 'Jakarta', 'Kopi Gayo', 14, 55000.00),
('2026-06-04', 'Bandung', 'Teh Melati', 9, 25000.00),
('2026-06-04', 'Surabaya', 'Kopi Toraja', 16, 62000.00),
('2026-06-05', 'Jakarta', 'Kopi Gayo', 11, 55000.00),
('2026-06-05', 'Bandung', 'Teh Melati', 10, 25000.00),
('2026-06-05', 'Surabaya', 'Kopi Toraja', 17, 62000.00);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 15 rows affected (0.00 sec)
Records: 15 Duplicates: 0 Warnings: 0Deret Jakarta terbaca 10, 12, 9, 14, 11 — naik, turun, naik, turun, pola zigzag yang pas untuk pengamatan penghalusan. Kita mulai dari satu kota agar pergeseran jendela terlihat teliti sebelum ketiga kota digabung.
Rata-rata Bergerak Tiga Periode #
Query inti chapter ini memasang frame ROWS BETWEEN 2 PRECEDING AND CURRENT ROW pada AVG:
SELECT tanggal, jumlah,
AVG(jumlah) OVER (ORDER BY tanggal
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rata3
FROM penjualan
WHERE kota = 'Jakarta'
ORDER BY tanggal;Output yang Dihasilkan:
+------------+--------+---------+
| tanggal | jumlah | rata3 |
+------------+--------+---------+
| 2026-06-01 | 10 | 10.0000 |
| 2026-06-02 | 12 | 11.0000 |
| 2026-06-03 | 9 | 10.3333 |
| 2026-06-04 | 14 | 11.6667 |
| 2026-06-05 | 11 | 11.3333 |
+------------+--------+---------+
5 rows in set (0.00 sec)Kolom rata3 terbaca 10.0000, 11.0000, 10.3333, 11.6667, 11.3333. Mari kita bedah cara kerjanya baris demi baris: frame ROWS BETWEEN 2 PRECEDING AND CURRENT ROW mengambil dua baris sebelum posisi saat ini ditambah baris saat ini — tepat tiga baris — lalu AVG(jumlah) merata-ratakan isinya, dan jendela itu digeser satu baris untuk setiap posisi berikutnya. Baris 2026-06-03 merata-ratakan 10, 12, 9 menghasilkan 10.3333; baris 2026-06-04 merata-ratakan 12, 9, 14 menghasilkan 11.6667; baris 2026-06-05 merata-ratakan 9, 14, 11 menghasilkan 11.3333. AVG atas kolom INT selalu menghasilkan empat angka desimal — 10.0000 bukan 10 — dan keempat desimal itu deterministik, sehingga kolom rata-rata bergerak aman dibandingkan ulang kapan pun.
Dua baris pertama memuat kejutan kecil yang wajar: AVG di sana hanya merata-ratakan dua baris (10, 12 → 11.0000) lalu satu baris (10 → 10.0000), karena di tepi deret tidak ada cukup dua baris sebelumnya. Frame berperilaku seperti jendela yang baru terbuka — menampilkan sebanyak mungkin baris yang tersedia di dalam rentangnya, bukan error atau NULL.
Jendela per Kota dengan PARTITION BY #
Laporan tiga kota sekaligus tinggal menambahkan partisi, sama seperti pola LAG dan running total:
SELECT kota, tanggal, jumlah,
AVG(jumlah) OVER (PARTITION BY kota ORDER BY tanggal
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rata3
FROM penjualan
ORDER BY kota, tanggal;Output yang Dihasilkan:
+----------+------------+--------+---------+
| kota | tanggal | jumlah | rata3 |
+----------+------------+--------+---------+
| Bandung | 2026-06-01 | 6 | 6.0000 |
| Bandung | 2026-06-02 | 8 | 7.0000 |
| Bandung | 2026-06-03 | 7 | 7.0000 |
| Bandung | 2026-06-04 | 9 | 8.0000 |
| Bandung | 2026-06-05 | 10 | 8.6667 |
| Jakarta | 2026-06-01 | 10 | 10.0000 |
| Jakarta | 2026-06-02 | 12 | 11.0000 |
| Jakarta | 2026-06-03 | 9 | 10.3333 |
| Jakarta | 2026-06-04 | 14 | 11.6667 |
| Jakarta | 2026-06-05 | 11 | 11.3333 |
| Surabaya | 2026-06-01 | 15 | 15.0000 |
| Surabaya | 2026-06-02 | 13 | 14.0000 |
| Surabaya | 2026-06-03 | 18 | 15.3333 |
| Surabaya | 2026-06-04 | 16 | 15.6667 |
| Surabaya | 2026-06-05 | 17 | 17.0000 |
+----------+------------+--------+---------+
15 rows in set (0.00 sec)Lima belas baris keluar membawa tiga jendela bergerak yang saling tak terganggu: Jakarta 10.0000 → 11.0000 → 10.3333 → 11.6667 → 11.3333, Bandung 6.0000 → 7.0000 → 7.0000 → 8.0000 → 8.6667, dan Surabaya 15.0000 → 14.0000 → 15.3333 → 15.6667 → 17.0000. Efek tepi pun direset per partisi — baris pertama tiap kota kembali dirata-ratakan sendirian, karena PARTITION BY kota memastikan jendela Bandung tidak pernah menyentuh baris Jakarta meski tanggalnya sama. Urutan jendela memakai ORDER BY tanggal di dalam partisi; tanpa partisi, rata-rata 2026-06-01 Jakarta akan tercampur nilai Bandung dan Surabaya yang berbagi tanggal sama.
Lebar Jendela: Dua vs Tiga Periode #
Lebar jendela adalah kendali utama rata-rata bergerak: sempit berarti responsif, lebar berarti halus. Kita adu keduanya pada deret Jakarta dalam satu query:
SELECT tanggal, jumlah,
AVG(jumlah) OVER (ORDER BY tanggal
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS rata2,
AVG(jumlah) OVER (ORDER BY tanggal
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rata3
FROM penjualan
WHERE kota = 'Jakarta'
ORDER BY tanggal;Output yang Dihasilkan:
+------------+--------+---------+---------+
| tanggal | jumlah | rata2 | rata3 |
+------------+--------+---------+---------+
| 2026-06-01 | 10 | 10.0000 | 10.0000 |
| 2026-06-02 | 12 | 11.0000 | 11.0000 |
| 2026-06-03 | 9 | 10.5000 | 10.3333 |
| 2026-06-04 | 14 | 11.5000 | 11.6667 |
| 2026-06-05 | 11 | 12.5000 | 11.3333 |
+------------+--------+---------+---------+
5 rows in set (0.00 sec)Perhatikan kolom rata2 mengekor deret asli dengan lebih rapat: 11.0000, 10.5000, 11.5000, 12.5000 masih bergoyang mengikuti zigzag 12, 9, 14, 11. Kolom rata3 lebih datar — 10.3333, 11.6667, 11.3333 — karena setiap nilainya menyerap tiga hari sekaligus. Membaca trennya kini jauh lebih mudah: rata-rata bergerak tiga periode Jakarta naik stabil dari 10.0000 menuju 11.3333 tanpa sekali pun turun, padahal deret aslinya turun dua kali. Semakin lebar jendela, semakin kuat penghalusan — dan semakin lambat kolom merespons perubahan arah yang sungguhan; rata-rata 30 periode butuh 15 hari untuk “menyadari” tren baru. Pilihan lebar selalu kompromi antara dua sifat itu.
Membersihkan Database Latihan #
Rangkaian latihan selesai dan database latihan kita jatuhkan kembali:
DROP DATABASE belajar_window;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- Window Functions — Frame Specification — MySQL 8.4 Reference Manual
- Window Function Descriptions — MySQL 8.4 Reference Manual
Rata-rata bergerak menuntaskan tiga aplikasi klasik window function berurutan: LAG membandingkan tetangga, running total mengakumulasi masa lalu, dan AVG dengan frame menghaluskan tren. Selama perjalanan itu kita menulis ROWS BETWEEN ... AND ... beberapa kali tanpa membongkar satu kata kunci yang belum dibahas: kenapa ROWS, apa bedanya dengan RANGE, dan bagaimana frame default bekerja. Chapter penutup section ini membedah ROWS melawan RANGE dengan data yang sengaja memuat nilai kembar — perbedaan yang selama ini hanya kami janjikan, kini dibuktikan.