Chapter sebelumnya memperkenalkan gagasan inti window function: baris tetap utuh, angka lintas baris menempel di sampingnya. Klausa OVER (...) adalah ruang kendali kita atas perilaku tersebut, dan dua komponen terpentingnya adalah PARTITION BY serta ORDER BY. PARTITION BY membagi baris-baris hasil menjadi jendela-jendela terpisah — satu jendela per kelompok — tanpa pernah menggabungkan baris seperti GROUP BY. ORDER BY mengatur urutan baris di dalam setiap jendela, urutan yang menentukan penomoran bagi fungsi ranking seperti ROW_NUMBER(). Pada chapter ini kita bedah keduanya satu per satu memakai tabel karyawan dari dua cabang kantor, lalu menggabungkannya dan menutup dengan perbandingan langsung terhadap GROUP BY.
Menyiapkan Tabel Latihan #
Kita pakai database latihan belajar_window dengan tabel karyawan sepuluh baris. Kolom departemen dan cabang akan menjadi kunci partisi, sementara gaji menjadi bahan perhitungan dan pengurutan:
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,
cabang VARCHAR(30) NOT NULL,
nama VARCHAR(50) NOT NULL,
gaji DECIMAL(10,2) NOT NULL
);
INSERT INTO karyawan (departemen, cabang, nama, gaji) VALUES
('IT', 'Jakarta', 'Budi', 12000000.00),
('IT', 'Jakarta', 'Siti', 15000000.00),
('IT', 'Bandung', 'Andi', 14000000.00),
('IT', 'Bandung', 'Rina', 8000000.00),
('Keuangan', 'Jakarta', 'Dewi', 11000000.00),
('Keuangan', 'Jakarta', 'Tono', 13000000.00),
('Keuangan', 'Bandung', 'Sari', 10000000.00),
('Pemasaran', 'Jakarta', 'Joko', 9000000.00),
('Pemasaran', 'Bandung', 'Nurul', 12500000.00),
('Pemasaran', 'Bandung', 'Rizky', 10500000.00);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0Perusahaan fiktif kita memiliki dua cabang, Jakarta dan Bandung, dengan tiga departemen yang keanggotaannya tidak merata: IT berisi empat karyawan, Keuangan tiga, dan Pemasaran tiga. Nilai gaji semuanya berbeda sehingga setiap pengurutan pada chapter ini bersifat deterministik — kelak saat membahas RANK, kita justru akan sengaja membuat nilai yang sama persis.
Membagi Jendela dengan PARTITION BY #
OVER (PARTITION BY kolom) memerintahkan MySQL membentuk satu jendela untuk setiap nilai berbeda dari kolom tersebut, lalu menjalankan fungsi pada setiap jendela secara terpisah. Mari kita hitung total gaji per departemen:
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 |
+-------+------------+-------------+------------------+
| Andi | IT | 14000000.00 | 49000000.00 |
| Budi | IT | 12000000.00 | 49000000.00 |
| Rina | IT | 8000000.00 | 49000000.00 |
| Siti | IT | 15000000.00 | 49000000.00 |
| Dewi | Keuangan | 11000000.00 | 34000000.00 |
| Sari | Keuangan | 10000000.00 | 34000000.00 |
| Tono | Keuangan | 13000000.00 | 34000000.00 |
| Joko | Pemasaran | 9000000.00 | 32000000.00 |
| Nurul | Pemasaran | 12500000.00 | 32000000.00 |
| Rizky | Pemasaran | 10500000.00 | 32000000.00 |
+-------+------------+-------------+------------------+
10 rows in set (0.00 sec)Sepuluh baris keluar — jumlahnya persis sama dengan tabel sumber, dan setiap baris membawa total gaji departemennya sendiri. Mari kita bedah pembagian kerjanya: PARTITION BY departemen membagi sepuluh baris menjadi tiga jendela — IT berisi Budi, Siti, Andi, dan Rina; Keuangan berisi Dewi, Tono, dan Sari; Pemasaran berisi Joko, Nurul, dan Rizky. Fungsi SUM(gaji) lalu dihitung terpisah pada tiap jendela: IT menghasilkan 49000000.00 (12000000 + 15000000 + 14000000 + 8000000), Keuangan 34000000.00, dan Pemasaran 32000000.00. Setiap hasil ditempel ke semua baris anggota jendelanya masing-masing — keempat baris IT sama-sama menampilkan 49000000.00. Inilah bukti kerja PARTITION BY yang tidak pernah menggabungkan baris: jumlah baris masuk sama dengan jumlah baris keluar, dan kolom-kolom detail seperti nama serta gaji tetap hidup berdampingan dengan angka ringkasan.
PARTITION BY Multi Kolom #
Partisi bisa dibentuk dari kombinasi lebih dari satu kolom — MySQL membuat satu jendela untuk setiap kombinasi nilai yang unik, persis seperti GROUP BY multi kolom membentuk kelompok per kombinasi. Kita tambahkan dimensi cabang ke dalam partisi:
SELECT nama, departemen, cabang, gaji,
SUM(gaji) OVER (PARTITION BY departemen, cabang) AS total_kelompok
FROM karyawan
ORDER BY departemen, cabang, nama;Output yang Dihasilkan:
+-------+------------+---------+-------------+----------------+
| nama | departemen | cabang | gaji | total_kelompok |
+-------+------------+---------+-------------+----------------+
| Andi | IT | Bandung | 14000000.00 | 22000000.00 |
| Rina | IT | Bandung | 8000000.00 | 22000000.00 |
| Budi | IT | Jakarta | 12000000.00 | 27000000.00 |
| Siti | IT | Jakarta | 15000000.00 | 27000000.00 |
| Sari | Keuangan | Bandung | 10000000.00 | 10000000.00 |
| Dewi | Keuangan | Jakarta | 11000000.00 | 24000000.00 |
| Tono | Keuangan | Jakarta | 13000000.00 | 24000000.00 |
| Nurul | Pemasaran | Bandung | 12500000.00 | 23000000.00 |
| Rizky | Pemasaran | Bandung | 10500000.00 | 23000000.00 |
| Joko | Pemasaran | Jakarta | 9000000.00 | 9000000.00 |
+-------+------------+---------+-------------+----------------+
10 rows in set (0.00 sec)Sepuluh baris tetap keluar, tetapi totalnya kini terbagi lebih halus menjadi enam jendela: IT Jakarta 27000000.00, IT Bandung 22000000.00, Keuangan Jakarta 24000000.00, Keuangan Bandung 10000000.00, Pemasaran Jakarta 9000000.00, dan Pemasaran Bandung 23000000.00. Perhatikan dua jendela yang hanya berisi satu baris — Sari di Keuangan Bandung dan Joko di Pemasaran Jakarta. Pada jendela beranggota tunggal, SUM(gaji) menghasilkan gaji anggotanya sendiri; tidak ada yang salah, jendela kecil tetap jendela sah. Jumlah keenam kelompok (27000000 + 22000000 + 24000000 + 10000000 + 9000000 + 23000000) tepat 115000000.00, sama dengan total seluruh gaji, karena partisi hanya membagi baris tanpa pernah menggandakan atau membuangnya.
ORDER BY di Dalam Jendela #
Komponen kedua spesifikasi OVER adalah ORDER BY, dan perannya berbeda dari ORDER BY biasa di akhir query. ORDER BY di dalam OVER mengatur urutan baris di dalam jendela — urutan internal yang dipakai oleh fungsi-fungsi ranking untuk menentukan siapa nomor satu, siapa nomor dua, dan seterusnya. Bagi fungsi ranking seperti ROW_NUMBER() dan RANK(), urutan ini menentukan langsung angka yang dihasilkan; bagi fungsi agregat seperti SUM(), kehadiran ORDER BY diam-diam mengubah cakupan hitung menjadi akumulasi berjalan — perilaku yang akan kita bedah penuh di chapter running total. Mari kita lihat bentuk paling mudahnya, penomoran seluruh karyawan dari gaji tertinggi:
SELECT nama, departemen, gaji,
ROW_NUMBER() OVER (ORDER BY gaji DESC) AS nomor_urut
FROM karyawan
ORDER BY gaji DESC;Output yang Dihasilkan:
+-------+------------+-------------+------------+
| nama | departemen | gaji | nomor_urut |
+-------+------------+-------------+------------+
| Siti | IT | 15000000.00 | 1 |
| Andi | IT | 14000000.00 | 2 |
| Tono | Keuangan | 13000000.00 | 3 |
| Nurul | Pemasaran | 12500000.00 | 4 |
| Budi | IT | 12000000.00 | 5 |
| Dewi | Keuangan | 11000000.00 | 6 |
| Rizky | Pemasaran | 10500000.00 | 7 |
| Sari | Keuangan | 10000000.00 | 8 |
| Joko | Pemasaran | 9000000.00 | 9 |
| Rina | IT | 8000000.00 | 10 |
+-------+------------+-------------+------------+
10 rows in set (0.00 sec)Siti dengan gaji 15000000.00 mendapat nomor 1, Andi 2, Tono 3, hingga Rina dengan gaji 8000000.00 mendapat nomor 10. Urutan ini berasal dari ORDER BY gaji DESC yang berada di dalam tanda kurung OVER. ORDER BY gaji DESC di baris terakhir query berperan berbeda: klausa tersebut hanya mengatur tampilan hasil di layar. Keduanya kebetulan sama pada contoh di atas, tetapi bila klausa terakhir diganti misalnya menjadi ORDER BY nama, penomoran tetap mengikuti gaji tertinggi — hanya barisnya tampil urut menurut nama. Dua urutan berbeda, dua tanggung jawab berbeda.
Menggabungkan PARTITION BY dan ORDER BY #
Kedua komponen digabung menjadi spesifikasi jendela yang paling sering dipakai: partisi menentukan kelompok, urutan menentukan posisi di dalam kelompok. Kita nomori karyawan tiap departemen mulai dari gaji tertinggi:
SELECT nama, departemen, gaji,
ROW_NUMBER() OVER (PARTITION BY departemen ORDER BY gaji DESC) AS nomor_dept
FROM karyawan
ORDER BY departemen, nomor_dept;Output yang Dihasilkan:
+-------+------------+-------------+------------+
| nama | departemen | gaji | nomor_dept |
+-------+------------+-------------+------------+
| Siti | IT | 15000000.00 | 1 |
| Andi | IT | 14000000.00 | 2 |
| Budi | IT | 12000000.00 | 3 |
| Rina | IT | 8000000.00 | 4 |
| Tono | Keuangan | 13000000.00 | 1 |
| Dewi | Keuangan | 11000000.00 | 2 |
| Sari | Keuangan | 10000000.00 | 3 |
| Nurul | Pemasaran | 12500000.00 | 1 |
| Rizky | Pemasaran | 10500000.00 | 2 |
| Joko | Pemasaran | 9000000.00 | 3 |
+-------+------------+-------------+------------+
10 rows in set (0.00 sec)Penomoran berjalan mandiri di setiap jendela: IT bernomor 1 sampai 4 (Siti, Andi, Budi, Rina), Keuangan 1 sampai 3 (Tono, Dewi, Sari), dan Pemasaran 1 sampai 3 (Nurul, Rizky, Joko). Setiap jendela memiliki hitungan sendiri yang dimulai dari 1 — inilah efek reset per partisi, perilaku yang sama akan kita jumpai pada semua window function. Bandingkan dengan query sebelumnya yang tanpa PARTITION BY: penomoran global berlangsung 1 sampai 10 tanpa pernah kembali ke 1. Kombinasi PARTITION BY departemen ORDER BY gaji DESC inilah yang menjadi fondasi pola top-N per kelompok — misalnya “dua karyawan bergaji tertinggi di tiap departemen” — yang akan kita bangun penuh di chapter ROW_NUMBER.
GROUP BY vs PARTITION BY #
Kita tutup dengan perbandingan langsung. Query GROUP BY berikut dan query window sebelumnya menjawab pertanyaan yang mirip — total gaji per departemen — dengan bentuk hasil yang berbeda:
SELECT departemen, COUNT(*) AS jumlah_karyawan, SUM(gaji) AS total_gaji
FROM karyawan
GROUP BY departemen
ORDER BY departemen;Output yang Dihasilkan:
+------------+-----------------+-------------+
| departemen | jumlah_karyawan | total_gaji |
+------------+-----------------+-------------+
| IT | 4 | 49000000.00 |
| Keuangan | 3 | 34000000.00 |
| Pemasaran | 3 | 32000000.00 |
+------------+-----------------+-------------+
3 rows in set (0.00 sec)Tiga baris keluar, satu per departemen, dengan isi yang kini murni rekap. Tabel berikut merangkum perbedaan perilaku keduanya:
| Aspek | GROUP BY | OVER (PARTITION BY …) |
|---|---|---|
| Jumlah baris hasil | satu per kelompok | sama dengan baris sumber |
| Kolom non-agregat di SELECT | wajib ikut GROUP BY | bebas menampilkan kolom apa pun |
| Baris dengan detail (nama, gaji) | hilang | tampil utuh |
| Fungsi ranking (ROW_NUMBER, RANK) | tidak tersedia | tersedia dengan ORDER BY di dalam jendela |
| Cocok untuk | laporan rekap, HAVING pada agregat | detail bermuatan konteks kelompok |
Satu perbedaan halus yang jarang disadari: GROUP BY membatasi kolom SELECT yang boleh muncul (aturan only_full_group_by), sedangkan window function tidak — jendela hanyalah konteks perhitungan, bukan pembatas kolom. Sebaliknya, hasil agregat GROUP BY bisa disaring dengan HAVING, sementara hasil window function tidak bisa disaring langsung di WHERE karena dihitung setelah WHERE selesai — jebakan yang akan kita buktikan dengan blok error di chapter berikutnya.
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
- SELECT Statement — MySQL 8.4 Reference Manual
- Window Function Descriptions — MySQL 8.4 Reference Manual
Kendali jendela kini lengkap di tangan kita: PARTITION BY membagi kelompok tanpa meruntuhkan baris, ORDER BY mengatur urutan di dalamnya, dan keduanya digabungkan untuk penomoran per kelompok. Chapter berikutnya membedah ROW_NUMBER() sampai ke akarnya — nomor urut global, nomor urut per kelompok, pola top-N per grup dengan CTE, hingga trik deduplikasi baris.