Sepanjang section agregasi kita mengandalkan COUNT, SUM, dan AVG yang bekerja dengan satu pola: banyak baris masuk, satu baris keluar. Pola itu gagal begitu laporan menuntut konteks antar baris — misalnya “peringkat ke berapa gaji Budi dibanding semua karyawan?”, “berapa penjualan hari ini dibanding kemarin?”, atau “berapa persen kontribusi transaksi ini terhadap total toko?”. Pertanyaan-pertanyaan tersebut menginginkan setiap baris tetap tampil, hanya saja dengan tambahan angka hasil hitung lintas baris. MySQL 8 menjawab kebutuhan tersebut dengan window function: fungsi yang menghitung agregasi di atas “jendela” sekumpulan baris, lalu menempelkan hasilnya pada setiap baris satu per satu. Pada chapter ini kita bedah dulu perbedaan mendasarnya dengan agregat biasa, mencoba bentuk paling sederhana OVER(), lalu memetakan kapan window function menjadi alat yang tepat.
Menyiapkan Tabel Latihan #
Kita pakai database latihan belajar_window dengan tabel karyawan berisi enam baris dari tiga departemen. Nilai gaji sengaja dibuat mudah dihitung manual sehingga kita bisa memverifikasi setiap hasil dengan cepat:
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 karyawan (
id INT AUTO_INCREMENT PRIMARY KEY,
departemen VARCHAR(30) NOT NULL,
nama VARCHAR(50) NOT NULL,
gaji DECIMAL(10,2) NOT NULL
);
INSERT INTO karyawan (departemen, nama, gaji) VALUES
('IT', 'Budi', 12000000.00),
('IT', 'Siti', 15000000.00),
('Keuangan', 'Dewi', 11000000.00),
('Keuangan', 'Tono', 13000000.00),
('Pemasaran', 'Rina', 9500000.00),
('Pemasaran', 'Joko', 12500000.00);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0Tabel ini sengaja kecil supaya setiap perbandingan agregat biasa dan window function pada chapter ini mudah dijejak baris demi baris. Setiap departemen memiliki tepat dua karyawan sehingga penjumlahan gaji per departemen bisa kita hitung di kepala: IT total 27000000.00, Keuangan total 24000000.00, dan Pemasaran total 22000000.00.
Agregat Biasa: Banyak Baris Runtuh Menjadi Satu #
Agregat biasa merangkum banyak baris menjadi satu baris hasil. Bentuk paling ekstrem terjadi tanpa GROUP BY, ketika seluruh tabel menjadi satu kelompok:
SELECT SUM(gaji) AS total_gaji
FROM karyawan;Output yang Dihasilkan:
+-------------+
| total_gaji |
+-------------+
| 73000000.00 |
+-------------+
1 row in set (0.00 sec)Enam baris runtuh menjadi satu baris berisi 73000000.00. Dengan GROUP BY departemen, tabel terbagi menjadi tiga keranjang dan hasilnya tiga baris:
SELECT departemen, SUM(gaji) AS total_gaji
FROM karyawan
GROUP BY departemen
ORDER BY departemen;Output yang Dihasilkan:
+------------+-------------+
| departemen | total_gaji |
+------------+-------------+
| IT | 27000000.00 |
| Keuangan | 24000000.00 |
| Pemasaran | 22000000.00 |
+------------+-------------+
3 rows in set (0.00 sec)Perhatikan harga yang kita bayar untuk angka-angka tersebut: detail per karyawan hilang dari hasil. Gaji 12000000.00 milik Budi tidak lagi terlihat — yang tersisa hanya total departemennya. Jumlah baris hasil ditentukan oleh jumlah kelompok, bukan oleh jumlah baris asli. Perilaku “runtuh” inilah yang membedakan agregat biasa, dan pola ini memang tepat untuk laporan rekap. Masalah muncul ketika laporan justru membutuhkan kedua hal sekaligus: baris detail tetap tampil dan angka ringkasan menempel di sampingnya.
Window Function: Setiap Baris Tetap Tampil #
Window function memakai fungsi agregat yang sama, tetapi klausa OVER mengubah cara hasil dibagikan. Mari kita hitung total gaji per departemen sekali lagi, kali ini tanpa GROUP BY:
SELECT nama, departemen, gaji,
SUM(gaji) OVER (PARTITION BY departemen) AS total_departemen
FROM karyawan
ORDER BY departemen, nama;Output yang Dihasilkan:
+------+------------+-------------+------------------+
| nama | departemen | gaji | total_departemen |
+------+------------+-------------+------------------+
| Budi | IT | 12000000.00 | 27000000.00 |
| Siti | IT | 15000000.00 | 27000000.00 |
| Dewi | Keuangan | 11000000.00 | 24000000.00 |
| Tono | Keuangan | 13000000.00 | 24000000.00 |
| Joko | Pemasaran | 12500000.00 | 22000000.00 |
| Rina | Pemasaran | 9500000.00 | 22000000.00 |
+------+------------+-------------+------------------+
6 rows in set (0.00 sec)Enam baris keluar — tepat enam, sama dengan jumlah baris tabel. Mari kita bedah mekanismenya baris per baris: klausa OVER (PARTITION BY departemen) memerintahkan MySQL membagi enam baris ke dalam tiga jendela menurut nilai departemen, sehingga terbentuk jendela IT berisi Budi dan Siti, jendela Keuangan berisi Dewi dan Tono, serta jendela Pemasaran berisi Rina dan Joko. Fungsi SUM(gaji) kemudian dijalankan terpisah pada setiap jendela — jendela IT menghasilkan 27000000.00, Keuangan 24000000.00, Pemasaran 22000000.00 — dan hasilnya ditempel pada setiap baris anggota jendela. Karena itu kedua baris IT sama-sama menampilkan 27000000.00: angka yang sama dihitung sekali per jendela, lalu disalin ke semua anggotanya. Perbandingannya kini tampak jelas: GROUP BY menghasilkan tiga baris rekap, sedangkan window function menghasilkan enam baris detail yang masing-masing membawa konteks departemennya.
| Aspek | Agregat + GROUP BY | Window function |
|---|---|---|
| Jumlah baris hasil | satu per kelompok | sama dengan baris sumber |
| Detail per baris | hilang | tetap tampil |
| Nilai agregat | satu baris per kelompok | disalin ke setiap anggota jendela |
| Cocok untuk | laporan rekap murni | detail + konteks lintas baris |
OVER() Kosong: Seluruh Hasil Menjadi Satu Jendela #
Spesifikasi jendela bisa dikosongkan sama sekali. OVER() tanpa PARTITION BY dan tanpa ORDER BY menjadikan seluruh hasil query sebagai satu jendela tunggal:
SELECT nama, gaji,
SUM(gaji) OVER () AS total_semua,
COUNT(*) OVER () AS jumlah_karyawan
FROM karyawan
ORDER BY nama;Output yang Dihasilkan:
+------+-------------+-------------+-----------------+
| nama | gaji | total_semua | jumlah_karyawan |
+------+-------------+-------------+-----------------+
| Budi | 12000000.00 | 73000000.00 | 6 |
| Dewi | 11000000.00 | 73000000.00 | 6 |
| Joko | 12500000.00 | 73000000.00 | 6 |
| Rina | 9500000.00 | 73000000.00 | 6 |
| Siti | 15000000.00 | 73000000.00 | 6 |
| Tono | 13000000.00 | 73000000.00 | 6 |
+------+-------------+-------------+-----------------+
6 rows in set (0.00 sec)Setiap baris kini membawa 73000000.00 dan 6 — total dan jumlah baris dari jendela yang sama, yaitu seluruh tabel. Pola ini kelihatan sederhana, tetapi justru menjadi fondasi perhitungan yang sulit dilakukan agregat biasa, misalnya persentase kontribusi tiap gaji terhadap total:
SELECT nama, gaji,
ROUND(gaji / SUM(gaji) OVER () * 100, 2) AS porsi_persen
FROM karyawan
ORDER BY porsi_persen DESC, nama;Output yang Dihasilkan:
+------+-------------+--------------+
| nama | gaji | porsi_persen |
+------+-------------+--------------+
| Siti | 15000000.00 | 20.55 |
| Tono | 13000000.00 | 17.81 |
| Joko | 12500000.00 | 17.12 |
| Budi | 12000000.00 | 16.44 |
| Dewi | 11000000.00 | 15.07 |
| Rina | 9500000.00 | 13.01 |
+------+-------------+--------------+
6 rows in set (0.00 sec)Query agregat biasa tidak bisa menghasilkan tabel di atas dalam satu kali jalan: SUM global meruntuhkan tabel, sementara kolom gaji per baris justru harus tetap hidup. Dengan OVER(), pembagi SUM(gaji) OVER () dihitung sekali untuk seluruh jendela, lalu setiap baris membagi gajinya sendiri dengan angka tersebut — gaji Siti 15000000.00 menghasilkan porsi 20.55 persen, gaji Rina 9500000.00 menghasilkan 13.01 persen.
Anatomi Sintaks dan Kapan Window Function Berguna #
Bentuk umum window function selalu memuat dua bagian: fungsi dan spesifikasi jendela.
nama_fungsi(ekspresi) OVER (
[PARTITION BY kolom, ...]
[ORDER BY kolom, ...]
[spesifikasi frame]
)Ada dua keluarga fungsi yang bisa dipakai. Pertama, fungsi agregat klasik (SUM, COUNT, AVG, MIN, MAX) yang berubah peran begitu ditambah OVER — dari meruntuhkan baris menjadi menghitung per jendela. Kedua, fungsi khusus window seperti ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), dan LEAD() yang sama sekali tidak punya makna tanpa OVER. Diagram berikut memetakan bagaimana satu kumpulan baris mengalir melalui OVER:
Kapan window function paling berguna? Kebutuhan analisis berikut hampir selalu lebih ringkas dinyatakan dengan window function: peringkat (siapa juara satu, siapa yang seri), nomor urut per kelompok (top-N per departemen), perbandingan antar waktu (penjualan hari ini vs kemarin), akumulasi berjalan (saldo tabungan dari mutasi), dan rata-rata bergerak untuk menghaluskan tren. Tabel berikut memetakan kebutuhan ke fungsi yang akan kita kuasai sepanjang section ini:
| Kebutuhan analisis | Fungsi inti |
|---|---|
| Nomor urut 1..n, termasuk per kelompok | ROW_NUMBER() |
| Peringkat dengan perlakuan seri | RANK(), DENSE_RANK() |
| Nilai baris sebelum/sesudahnya | LAG(), LEAD() |
| Akumulasi berjalan | SUM() OVER (ORDER BY ...) |
| Rata-rata bergerak | AVG() OVER (... ROWS BETWEEN ...) |
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 Function Concepts and Syntax — MySQL 8.4 Reference Manual
- Window Function Descriptions — MySQL 8.4 Reference Manual
- Aggregate Function Descriptions — MySQL 8.4 Reference Manual
Kita kini memegang konsep intinya: agregat biasa meruntuhkan baris, window function menjaga baris tetap utuh sambil membawa hasil hitung lintas baris. Spesifikasi jendela OVER (...) adalah ruang kendali utamanya — dua komponen pertama yang akan kita bedah mendalam adalah PARTITION BY yang membagi jendela per kelompok dan ORDER BY yang mengatur urutan di dalamnya, topik chapter berikutnya.