Topik ini membahas HAVING, klausa yang menyaring kelompok setelah agregasi dijalankan, sekaligus menegaskan batasnya dengan WHERE. Pertanyaan yang dilayani HAVING selalu menyangkut watak kelompok, misalnya kategori mana yang koleksinya mencukupi, dan pertanyaan semacam itu tidak dapat dijawab sebelum kelompoknya terbentuk.
Blok penyiapan dataset#
# siapkan ulang tabel dan data contoh untuk topik ini
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS peminjaman, buku, anggota, kategori CASCADE;
CREATE TABLE kategori (id SERIAL PRIMARY KEY, nama TEXT NOT NULL UNIQUE);
CREATE TABLE anggota (id SERIAL PRIMARY KEY, nama TEXT NOT NULL, email TEXT UNIQUE, bergabung DATE NOT NULL, aktif BOOLEAN NOT NULL DEFAULT TRUE);
CREATE TABLE buku (id SERIAL PRIMARY KEY, judul TEXT NOT NULL, penulis TEXT NOT NULL, tahun INTEGER NOT NULL, kategori_id INTEGER REFERENCES kategori(id));
CREATE TABLE peminjaman (id SERIAL PRIMARY KEY, anggota_id INTEGER NOT NULL REFERENCES anggota(id), buku_id INTEGER NOT NULL REFERENCES buku(id), tanggal_pinjam DATE NOT NULL, tanggal_kembali DATE);
INSERT INTO kategori (nama) VALUES ('Pemrograman'), ('Sains'), ('Sejarah'), ('Fiksi'), ('Statistika');
INSERT INTO anggota (nama, email, bergabung, aktif) VALUES ('Ali Rahman', '[email protected]', '2024-01-15', TRUE), ('Budi Santoso', '[email protected]', '2024-03-02', TRUE), ('Citra Kirana', '[email protected]', '2024-05-20', FALSE), ('Dewi Lestari', '[email protected]', '2024-08-11', TRUE), ('Eka Pratama', NULL, '2025-01-30', TRUE);
INSERT INTO buku (judul, penulis, tahun, kategori_id) VALUES ('Dasar Basis Data', 'Sari Wulandari', 2021, 1), ('Jaringan Komputer', 'Ahmad Fauzi', 2019, 1), ('Fisika untuk Pemula', 'Rina Marlina', 2020, 2), ('Kimia Organik', 'Rina Marlina', 2022, 2), ('Sejarah Nusantara', 'Bambang Wijaya', 2018, 3), ('Cerita Kota Lama', 'Nur Aisyah', 2023, 4), ('Statistika Terapan', 'Sari Wulandari', 2022, 5), ('Algoritma dan Struktur Data', 'Ahmad Fauzi', 2021, 1);
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, tanggal_kembali) VALUES (1, 1, '2025-03-01', '2025-03-15'), (1, 7, '2025-03-05', '2025-03-20'), (2, 3, '2025-03-10', NULL), (3, 5, '2025-02-20', '2025-03-01'), (2, 1, '2025-04-02', NULL), (4, 6, '2025-04-07', NULL), (5, 8, '2025-04-09', '2025-04-25'), (4, 2, '2025-05-01', NULL);
SQLMenyaring kelompok menurut agregatnya#
HAVING ditempatkan sesudah GROUP BY dan menilai satu ekspresi per kelompok, sehingga agregatnya boleh dirujuk langsung. Query berikut menampilkan hanya penulis yang karyanya lebih dari satu.
# penulis dengan lebih dari satu karya
psql -U postgres -d perpustakaan -c "SELECT penulis, COUNT(*) AS karya FROM buku GROUP BY penulis HAVING COUNT(*) > 1 ORDER BY penulis;" penulis | karya
----------------+-------
Ahmad Fauzi | 2
Rina Marlina | 2
Sari Wulandari | 2
(3 rows)Tiga penulis lolos, masing-masing dengan dua karya, sedangkan dua penulis dengan satu karya tersaring. Kebiasaan yang baik memberi alias pada agregatnya untuk daftar hasil, sementara HAVING mengulang ekspresi COUNT(*)-nya, karena alias SELECT belum terbentuk pada saat HAVING dinilai.
HAVING dan WHERE berdampingan#
WHERE menyaring baris sebelum pengelompokan, sedangkan HAVING menyaring kelompok sesudahnya, dan query yang serius kerap memakai keduanya sekaligus. Menempatkan syarat baris di WHERE bukan sekadar gaya, karena baris yang tidak perlu tidak ikut membebani agregasi.
# kategori aktif dengan peminjaman selesai lebih dari satu
psql -U postgres -d perpustakaan -c "SELECT k.nama AS kategori, COUNT(p.id) AS kembali FROM kategori k JOIN buku b ON b.kategori_id = k.id JOIN peminjaman p ON p.buku_id = b.id WHERE p.tanggal_kembali IS NOT NULL GROUP BY k.nama HAVING COUNT(p.id) > 1 ORDER BY kembali DESC, kategori;" kategori | kembali
-------------+---------
Pemrograman | 2
(1 row)WHERE menyaring peminjaman yang sudah selesai sebelum pengelompokan, sedangkan HAVING menjaga hanya kategori dengan hitungan di atas satu. Apabila syarat peminjaman selesai dipindahkan ke HAVING, angkanya tetap benar tetapi kerjanya boros, karena baris yang ditolak itu ikut bergabung dan diagregasi terlebih dahulu.
Ambang yang melibatkan beberapa agregat#
Syarat HAVING boleh menggabungkan beberapa agregat sekaligus, karena seluruh angka kelompok sudah tersedia pada saat ia dinilai. Kebutuhan nyatanya menyeleksi kelompok yang hitungannya cukup banyak sekaligus cukup baru, sehingga kelompok kecil yang usang tidak ikut dilaporkan.
# penulis dengan dua karya atau lebih dan karya terbaru mulai 2021
psql -U postgres -d perpustakaan -c "SELECT penulis, COUNT(*) AS karya, MAX(tahun) AS terbaru FROM buku GROUP BY penulis HAVING COUNT(*) >= 2 AND MAX(tahun) >= 2021 ORDER BY penulis;" penulis | karya | terbaru
----------------+-------+---------
Ahmad Fauzi | 2 | 2021
Rina Marlina | 2 | 2022
Sari Wulandari | 2 | 2022
(3 rows)Dua syarat dinilai atas kelompok yang sama, dan penggabungannya dengan AND mengikuti logika boolean biasa. Apabila ambangnya berubah menurut kebijakan, angkanya diparameterkan pada lapisan aplikasi, sedangkan bentuk querynya tetap sama.
HAVING tanpa GROUP BY#
PostgreSQL mengizinkan HAVING tanpa GROUP BY, dan seluruh tabel diperlakukan sebagai satu kelompok. Bentuk ini melayani perbandingan total terhadap ambang, misalnya melaporkan jumlah peminjaman hanya bila mencapai target tertentu.
# laporkan total hanya bila memenuhi ambang
psql -U postgres -d perpustakaan -c "SELECT COUNT(*) AS total_pinjam FROM peminjaman HAVING COUNT(*) >= 8;" total_pinjam
--------------
8
(1 row)Keluarannya satu baris ketika ambang tercapai dan nol baris ketika tidak, perilaku yang berbeda dari WHERE tanpa baris yang juga mengosongkan hasil. Bentuk ini praktis pada laporan berkala yang menuntut pengecualian tampil kosong, bukan angka yang memicu alarm.
Kesalahan umum#
Kesalahan pertama adalah merujuk alias SELECT di dalam HAVING, yang ditolak karena aliasnya belum ada pada saat penilaian, dan perbaikannya mengulang ekspresi agregasinya. Kesalahan kedua adalah menempatkan syarat baris di HAVING, yang hasilnya benar tetapi kerjanya tidak hemat, dan aturan penempatannya mengikuti urutan logis klausa.
Kesalahan ketiga adalah mengira HAVING tanpa GROUP BY adalah kesalahan, padahal bentuknya sah dan bermakna untuk satu kelompok total. Kesalahan keempat adalah menulis ambang pada HAVING dengan literal yang menggantung tanpa komentar, sementara ambang bisnis yang disebut pada query produksi dinyatakan sebagai parameter yang jelas sumbernya.
Kebutuhan laporan yang sering menyusul adalah persentase terhadap total, dan HAVING berpadu dengan window function untuk menyelesaikannya dalam satu query, yaitu agregasi kelompok yang dibagi total agregasi window di atas hasilnya. Pola ini menegaskan pembagian tugas kedua alatnya, yaitu HAVING memilih kelompok yang dilaporkan, sedangkan window function menghitung konteks pembandingnya di dalam laporan. Percobaan menyelesaikan keduanya dengan satu subquery biasa justru mengembalikan pekerjaan yang telah diselesaikan alat yang tersedia.
Ringkasan#
- HAVING menyaring kelompok sesudah agregasi, dan agregatnya ditulis sebagai ekspresi, bukan alias SELECT.
- WHERE menyaring baris sebelum pengelompokan, sehingga syarat baris ditempatkan di WHERE demi kehematan dan kejelasan.
- HAVING tanpa GROUP BY memperlakukan seluruh tabel sebagai satu kelompok untuk kebutuhan ambang total.
- Alias SELECT baru terbentuk pada tahap SELECT, sehingga HAVING dan WHERE mengulang ekspresinya secara eksplisit.
Referensi#
- SELECT — PostgreSQL Documentation — Referensi klausa HAVING dan urutan penilaiannya.
- Grouping — PostgreSQL Documentation — Pengelompokan dan penyaringan kelompok pada dokumentasi resmi.
- Aggregate Functions — PostgreSQL Documentation — Fungsi agregasi yang menjadi bahan syarat HAVING.