Klausa WHERE menghentikan baris yang tidak memenuhi syarat sebelum agregasi berjalan, sehingga WHERE SUM(jumlah) > 5 tidak pernah sah — nilai SUM(jumlah) baru ada setelah pengelompokan selesai. MySQL menyediakan klausa HAVING khusus untuk menyaring hasil agregat: satu-satunya tempat where-condition boleh membandingkan fungsi agregat. Pada chapter ini kita membuktikan pembagian tugas WHERE dan HAVING, mempelajari urutan eksekusi logis yang menjelaskan mengapa keduanya tidak bisa saling menggantikan, lalu merangkai kombinasi lengkap WHERE–GROUP BY–HAVING–ORDER BY yang menjadi pola standar laporan harian.
Menyiapkan Tabel Latihan #
Kita lanjutkan memakai tabel penjualan yang sama dengan chapter GROUP BY: empat belas transaksi penjualan kopi di empat kota, dengan kolom pelanggan yang boleh NULL. Konsistensi data antar chapter membuat setiap angka hasil agregasi mudah dibandingkan dengan hasil chapter sebelumnya tanpa membuka ulang skrip:
DROP DATABASE IF EXISTS belajar_agregasi;
CREATE DATABASE belajar_agregasi;
USE belajar_agregasi;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(50) NOT NULL,
produk VARCHAR(50) NOT NULL,
jumlah INT NOT NULL,
harga DECIMAL(10,2) NOT NULL,
pelanggan VARCHAR(50) NULL
);
INSERT INTO penjualan (tanggal, kota, produk, jumlah, harga, pelanggan) VALUES
('2026-01-05', 'Jakarta', 'Kopi Gayo', 2, 55000.00, 'Budi'),
('2026-01-05', 'Bandung', 'Teh Melati', 3, 25000.00, 'Siti'),
('2026-01-06', 'Jakarta', 'Teh Melati', 1, 25000.00, NULL),
('2026-01-07', 'Surabaya', 'Kopi Toraja', 5, 62000.00, 'Andi'),
('2026-01-08', 'Bandung', 'Gula Aren', 4, 15000.00, NULL),
('2026-01-09', 'Jakarta', 'Gula Aren', 2, 15000.00, 'Rina'),
('2026-01-10', 'Yogyakarta', 'Kopi Gayo', 3, 55000.00, NULL),
('2026-01-11', 'Surabaya', 'Teh Melati', 2, 25000.00, 'Budi'),
('2026-01-12', 'Yogyakarta', 'Kopi Toraja', 1, 62000.00, NULL),
('2026-01-13', 'Jakarta', 'Kopi Toraja', 4, 62000.00, 'Siti'),
('2026-01-14', 'Bandung', 'Kopi Gayo', 1, 55000.00, NULL),
('2026-01-15', 'Surabaya', 'Gula Aren', 6, 15000.00, 'Rina'),
('2026-01-16', 'Jakarta', 'Kopi Gayo', 1, 55000.00, 'Dewi'),
('2026-01-17', 'Bandung', 'Teh Melati', 2, 25000.00, NULL);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 14 rows affected (0.00 sec)
Records: 14 Duplicates: 0 Warnings: 0Menyaring Hasil Agregat #
Pertanyaan yang tidak bisa dijawab WHERE misalnya berbunyi: kota mana yang total unit terjualnya lebih dari lima? Nilai SUM(jumlah) dihitung per kelompok kota, sehingga penyaringnya harus diletakkan setelah GROUP BY. Klausa HAVING mengisi posisi tersebut:
SELECT kota, SUM(jumlah) AS total_unit
FROM penjualan
GROUP BY kota
HAVING SUM(jumlah) > 5
ORDER BY kota;Output yang Dihasilkan:
+----------+------------+
| kota | total_unit |
+----------+------------+
| Bandung | 10 |
| Jakarta | 10 |
| Surabaya | 13 |
+----------+------------+
3 rows in set (0.00 sec)Hasilnya hanya memuat kota dengan total unit di atas lima. Bandung dengan total 10 unit, Jakarta dengan 10 unit, dan Surabaya dengan 13 unit lolos saringan, sedangkan Yogyakarta dengan hanya 4 unit tersaring keluar. Angka-angka ini bisa kita hitung ulang dari empat belas baris data secara manual — kebiasaan yang sebaiknya dipertahankan saat memverifikasi laporan agregasi.
WHERE dan HAVING Bekerja pada Tahap Berbeda #
Kekuatan sesungguhnya muncul saat keduanya dipakai bersamaan. Skenario berikutnya: dari transaksi produk kopi saja (bukan teh atau gula), kota mana yang total unitnya melebihi lima? WHERE memangkas baris berdasarkan produk sebelum pengelompokan; HAVING kemudian menyaring kelompok berdasarkan hasil SUM:
SELECT kota, SUM(jumlah) AS total_unit_kopi
FROM penjualan
WHERE produk LIKE 'Kopi%'
GROUP BY kota
HAVING SUM(jumlah) > 5
ORDER BY kota;Output yang Dihasilkan:
+---------+-----------------+
| kota | total_unit_kopi |
+---------+-----------------+
| Jakarta | 7 |
+---------+-----------------+
1 row in set (0.00 sec)Perhatikan bahwa Jakarta kini totalnya turun menjadi 7 unit karena transaksi Teh Melati dan Gula Aren di Jakarta sudah dibuang lebih dulu oleh WHERE. Bandung hilang seluruhnya — satu-satunya transaksi kopinya hanya 1 unit — meskipun total keseluruhan Bandung tadi 10 unit. Perbedaan kedua angka ini membuktikan bahwa WHERE dan HAVING menyaring objek yang berbeda: baris mentah versus kelompok hasil agregat.
Urutan eksekusi logis menjelaskan pembagian tugas tersebut. Meskipun kita menulis klausa dalam urutan SELECT–FROM–WHERE–GROUP BY–HAVING–ORDER BY, MySQL memprosesnya sebagai FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY:
| Tahap | Klausa | Objek yang disaring |
|---|---|---|
| 1 | FROM | Tabel sumber |
| 2 | WHERE | Baris individual |
| 3 | GROUP BY | Pembentukan kelompok |
| 4 | HAVING | Kelompok hasil agregat |
| 5 | SELECT | Kolom/ekspresi akhir |
| 6 | ORDER BY | Baris hasil akhir |
Tabel ini juga menjelaskan dua aturan penulisan lain: alias kolom yang dibuat di SELECT belum ada ketika WHERE dievaluasi, tetapi sudah tersedia saat ORDER BY dievaluasi. Perilaku HAVING berada di tengah — dan di titik ini MySQL memberi kelonggaran.
Alias Agregat di HAVING #
Standar SQL mewajibkan HAVING menulis ulang ekspresi agregat secara penuh, dan MySQL mendukung gaya tersebut seperti contoh sebelumnya. Sebagai tambahan yang tidak standar, MySQL juga mengizinkan HAVING merujuk alias yang dideklarasikan di SELECT:
SELECT kota, COUNT(*) AS jumlah_transaksi
FROM penjualan
GROUP BY kota
HAVING jumlah_transaksi >= 4
ORDER BY kota;Output yang Dihasilkan:
+---------+------------------+
| kota | jumlah_transaksi |
+---------+------------------+
| Bandung | 4 |
| Jakarta | 5 |
+---------+------------------+
2 rows in set (0.00 sec)Query ini menyaring kota dengan sedikitnya empat transaksi. Alias jumlah_transaksi merujuk COUNT(*) — perbandingan >= 4 berlaku pada hasil hitung tersebut. Kelonggaran ini nyaman dipakai, namun ingat bahwa portabilitasnya rendah: database lain umumnya menolak alias di HAVING, sehingga kode yang akan dipindahkan antar database sebaiknya tetap menulis ekspresi agregat secara penuh.
HAVING Tanpa GROUP BY #
HAVING juga bisa berdiri tanpa GROUP BY. Pada query agregat tanpa pengelompokan, seluruh tabel menjadi satu kelompok tunggal, dan HAVING menyaring kelompok tunggal tersebut — hasilnya baik satu baris (kondisi terpenuhi) maupun nol baris (tidak terpenuhi):
SELECT COUNT(*) AS total_transaksi, SUM(jumlah) AS total_unit
FROM penjualan
HAVING COUNT(*) > 10;Output yang Dihasilkan:
+-----------------+------------+
| total_transaksi | total_unit |
+-----------------+------------+
| 14 | 37 |
+-----------------+------------+
1 row in set (0.00 sec)Kondisi COUNT(*) > 10 bernilai benar karena tabel memuat empat belas baris, sehingga satu baris hasil agregat tetap tampil. Pola ini berguna sebagai gerbang sederhana: misalnya sebuah query dashboard hanya menampilkan angka bila data hari ini sudah terisi minimal sejumlah ambang tertentu, dan diam total bila belum.
Referensi #
- Aggregate Function Descriptions — MySQL 8.4 Reference Manual
- SELECT Statement — MySQL 8.4 Reference Manual
- GROUP BY Modifiers — MySQL 8.4 Reference Manual
Dengan HAVING, kotak peralatan laporan kita lengkap: WHERE memilih baris, GROUP BY mengelompokkan, HAVING menyaring kelompok, dan ORDER BY merapikan hasil. Bab berikutnya mengasah satu pola lanjutan yang kerap menggantikan laporan berkolom-kolom: agregasi dengan kondisi di dalam fungsi menggunakan CASE WHEN.