HAVING

Menyaring hasil agregasi dan perbedaan WHERE dengan HAVING.

Daftar isi

HAVING menyaring grup hasil pengelompokan setelah agregasi dihitung, sedangkan WHERE menyaring baris sebelum pengelompokan. Topik ini membahas letak keduanya pada alur evaluasi query, cara menggabungkannya, dan pemakaian agregat yang tidak muncul pada SELECT. Dataset peminjaman dan anggota yang sama dipakai di seluruh contoh, sehingga perbedaan hasilnya dapat dihitung ulang langsung dari datanya. Setiap blok SQL memuat pembuatan tabelnya sendiri, sehingga dapat dijalankan langsung dengan sqlite3 :memory:.

WHERE menyaring sebelum agregasi#

Klausa WHERE bekerja pada baris individual sebelum ada pengelompokan maupun agregasi. Baris yang tidak memenuhi syaratnya dibuang lebih dahulu, sehingga fungsi agregasi hanya melihat baris yang lolos. Query berikut menghitung banyaknya dan total denda yang nilainya sekurang-kurangnya 1.0.

sql Penyaringan baris denda sekurang-kurangnya 1.0
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT COUNT(*), SUM(denda) FROM peminjaman WHERE denda >= 1.0;
text Keluaran
3|4.0

Hanya tiga baris yang dendanya sekurang-kurangnya 1.0 yang lolos, yaitu denda 1.0 pada baris kedua, 2.0 pada baris kelima, dan 1.0 pada baris ketujuh, sehingga totalnya 4.0. Tiga baris dengan denda NULL ikut terbuang karena perbandingan NULL >= 1.0 tidak menghasilkan benar, sesuai logika tiga nilai yang dibahas pada modul dasar-query. Keluaran ini menjadi pembanding ketika syarat serupa dipindahkan ke tingkat grup.

HAVING menyaring grup#

Klausa HAVING mengambil alih setelah pengelompokan selesai. Syaratnya dievaluasi terhadap setiap grup, umumnya melalui fungsi agregasi, dan grup yang tidak memenuhi syarat dibuang utuh. Query berikut menampilkan anggota dengan total denda sekurang-kurangnya 1.0.

sql Penyaringan grup dengan total denda sekurang-kurangnya 1.0
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT id_anggota, COUNT(*), SUM(denda)
FROM peminjaman
GROUP BY id_anggota
HAVING SUM(denda) >= 1.0
ORDER BY id_anggota;
text Keluaran
1|2|1.5
3|2|2.5
5|2|1.0

Lima grup terbentuk dari data, tetapi hanya tiga yang lolos. Budi dan Dewi terbuang karena SUM(denda) pada kedua anggota itu bernilai NULL, dan perbandingan NULL >= 1.0 tidak bernilai benar. Perhatikan bahwa syarat ini tidak mungkin dipindahkan ke WHERE karena SUM(denda) belum ada sebelum pengelompokan, sehingga percobaan itu berakhir sebagai error sintaks.

HAVING dengan agregat di luar SELECT#

Syarat HAVING tidak wajib muncul pada daftar kolom SELECT. Agregat boleh dipakai semata untuk menyaring, lalu hasil akhirnya hanya menampilkan kolom yang dibutuhkan laporan. Dua query berikut menghasilkan informasi yang sama dengan bentuk keluaran yang berbeda.

sql Penyaringan dengan agregat yang ikut ditampilkan
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT id_anggota, COUNT(*)
FROM peminjaman
GROUP BY id_anggota
HAVING COUNT(*) >= 2
ORDER BY id_anggota;
text Keluaran
1|2
3|2
5|2
sql Penyaringan dengan agregat yang tidak ditampilkan
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT id_anggota
FROM peminjaman
GROUP BY id_anggota
HAVING COUNT(*) >= 2
ORDER BY id_anggota;
text Keluaran
1
3
5

Query pertama menampilkan jumlah bersama id anggota, sedangkan query kedua hanya menampilkan id anggota yang jumlah peminjamannya sekurang-kurangnya dua. COUNT(*) tetap dihitung pada keduanya, tetapi pada query kedua ia hanya berperan sebagai penyaring. Pola ini menjaga keluaran tetap ramping tanpa mengurangi daya saring HAVING, dan sering dipakai ketika laporan hanya butuh daftar kelompok yang memenuhi ambang tertentu.

Kombinasi WHERE dan HAVING#

Query nyata sering memakai keduanya sekaligus, dan masing-masing menjawab persoalan yang berbeda. WHERE memangkas baris yang tidak relevan sebelum pengelompokan, sedangkan HAVING memangkas grup setelah agregasi. Query berikut mencari anggota yang total dendanya mencapai 2.0 dengan hanya menghitung denda yang benar-benar tercatat.

sql WHERE dan HAVING pada satu query
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT a.nama, COUNT(*) AS jumlah, SUM(p.denda) AS total_denda
FROM peminjaman p
JOIN anggota a ON a.id = p.id_anggota
WHERE p.denda IS NOT NULL
GROUP BY a.nama
HAVING SUM(p.denda) >= 2.0
ORDER BY a.nama;
text Keluaran
Citra|2|2.5

WHERE p.denda IS NOT NULL membuang tiga baris denda NULL lebih dahulu, sehingga Budi dan Dewi tidak membentuk grup. Agregasi lalu menghitung total per nama, dan HAVING membuang grup dengan total di bawah 2.0. Hanya Citra yang lolos dengan total 2.5 dari dua peminjaman.

WHERE versus HAVING pada predikat yang sama#

Predikat yang serupa dapat menghasilkan keluaran yang berbeda tergantung letaknya. Syarat denda >= 1.0 yang ditaruh di WHERE pada bagian awal topik menghasilkan tiga baris dengan total 4.0, sedangkan syarat total yang ditaruh di HAVING menghasilkan tiga anggota dengan total 1.5, 2.5, dan 1.0. Keduanya sah, tetapi pertanyaannya berbeda, yakni banyaknya kejadian denda besar berbanding daftar anggota dengan total denda besar.

Bentuk HAVING lain yang lazim adalah menyaring berdasarkan rata-rata. Query berikut menampilkan anggota dengan rata-rata denda di atas 1.0.

sql Penyaringan grup berdasarkan rata-rata denda
CREATE TABLE anggota (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL
);
CREATE TABLE peminjaman (
  id INTEGER PRIMARY KEY,
  id_anggota INTEGER,
  id_buku INTEGER,
  tanggal_pinjam TEXT,
  denda REAL
);
INSERT INTO anggota (id, nama) VALUES
  (1, 'Ali'), (2, 'Budi'), (3, 'Citra'),
  (4, 'Dewi'), (5, 'Eka'), (6, 'Farhan');
INSERT INTO peminjaman (id, id_anggota, id_buku, tanggal_pinjam, denda) VALUES
  (1, 1, 1, '2026-03-02', 0.5),
  (2, 1, 3, '2026-03-04', 1.0),
  (3, 2, 2, '2026-03-05', NULL),
  (4, 3, 1, '2026-03-09', 0.5),
  (5, 3, 4, '2026-03-12', 2.0),
  (6, 4, 5, '2026-03-16', NULL),
  (7, 5, 3, '2026-03-19', 1.0),
  (8, 5, 6, '2026-03-23', NULL);

SELECT id_anggota, AVG(denda)
FROM peminjaman
GROUP BY id_anggota
HAVING AVG(denda) > 1.0
ORDER BY id_anggota;
text Keluaran
3|1.25

Hanya Citra yang lolos karena rata-rata dendanya 1.25. Ali berhenti di 0.75 dan Eka tepat 1.0 sehingga tidak melewati syarat > 1.0, sedangkan grup Budi dan Dewi terbuang karena rata-ratanya NULL. Tabel berikut merangkum perbedaan kedua klausa tersebut.

AspekWHEREHAVING
Waktu evaluasisebelum pengelompokansetelah agregasi
Objek yang disaringbaris individualgrup
Boleh memuat agregattidakya
Tujuan lazimmemangkas data mentahmenyaring hasil laporan

Kesalahan umum#

Menaruh fungsi agregasi di dalam WHERE. Mesin menolaknya sebagai error karena agregat belum ada sebelum pengelompokan, sehingga query tidak dapat dijalankan sama sekali. Pindahkan syaratnya ke HAVING, atau ubah menjadi syarat tingkat baris bila itu yang dimaksud.

Mengira HAVING menyaring baris. Grup yang lolos tetap mewakili seluruh barisnya, sehingga angka pada hasil adalah angka grup, bukan angka baris yang disaring. Gunakan WHERE untuk penyaringan baris dan biarkan HAVING bekerja pada tingkat grup.

Berharap grup yang agregatnya NULL ikut lolos perbandingan HAVING. Perbandingan apa pun terhadap NULL tidak bernilai benar, sehingga grup seperti Budi dan Dewi terbuang tanpa pesan apa pun. Tambahkan syarat IS NULL secara sengaja bila grup itu memang harus tampil pada laporan.

Ringkasan#

  • WHERE menyaring baris sebelum pengelompokan, sedangkan HAVING menyaring grup setelah agregasi dihitung.
  • Syarat HAVING boleh memakai agregat yang tidak muncul pada SELECT, sehingga keluaran tetap ramping.
  • WHERE dan HAVING dapat dipakai bersama, dan penempatan syarat tingkat baris pada WHERE lebih efisien.
  • Grup yang agregatnya NULL terbuang pada perbandingan HAVING karena logika tiga nilai.

Referensi#

  • SELECT — SQLite — Dokumentasi resmi klausa WHERE, GROUP BY, dan HAVING beserta urutan evaluasinya.