Operasi Seleksi: Memahami Perbedaan WHERE dan HAVING di SQL

aljabar relasional seleksi
Reading Time: 10 minutes

Photo by Edu Grande on Unsplash

Mengapa banyak developer SQL terjebak antara WHERE dan HAVING?


1. Pendahuluan

Bagi siapa saja yang baru menceburkan diri ke dunia data—baik sebagai Data Analyst, Software Engineer, atau Database Administrator—menulis query SQL sering kali terasa seperti seni yang menantang. Di antara sekian banyak klausa yang ada, ada dua kata kunci yang paling sering memicu perdebatan dan membuat dahi berkerut: WHERE dan HAVING.

Keresahannya selalu sama: “Bukankah keduanya sama-sama berfungsi untuk menyaring data? Kalau fungsinya sama, kenapa harus ada dua klausa yang berbeda? Kenapa query saya mendadak error saat mencoba mengganti WHERE dengan HAVING?”

Kebingungan ini sangat wajar. Di permukaan, WHERE dan HAVING memang terlihat seperti anak kembar yang melakukan pekerjaan yang sama, yaitu membuang data yang tidak diinginkan dan mempertahankan data yang kita butuhkan. Namun, jika kita melihat lebih dalam ke mesin database, keduanya adalah dua entitas yang sangat berbeda.

Spoiler singkatnya untuk Anda: Perbedaan terbesar antara WHERE dan HAVING terletak pada WAKTU eksekusi dan OBJEK yang mereka saring.

Ibarat proses seleksi masuk kerja, WHERE adalah tahap screening CV di awal—ia menyaring pelamar satu per satu secara individu sebelum mereka masuk ke dalam ruangan kelompok. Sementara itu, HAVING adalah tahap evaluasi akhir setelah pelamar dikelompokkan berdasarkan divisi—ia menilai hasil performa dari kelompok tersebut secara keseluruhan.

Di artikel ini, kita tidak hanya akan membahas teori dasar yang membosankan. Kita akan membongkar tuntas mengapa penyaringan data di waktu yang salah bisa merusak performa query Anda, melihat area “abu-abu” yang sering menjadi jebakan, serta melihat bagaimana kedua klausa ini bekerja sama dalam menyelesaikan kasus nyata di industri e-commerce hingga sistem deteksi kecurangan (fraud).


2. Anatomi & Urutan Eksekusi SQL: Di Mana Core Logic-nya?

Untuk memahami mengapa WHERE dan HAVING tidak bisa saling menggantikan, kita harus membongkar satu rahasia terbesar tentang cara kerja database: SQL tidak membaca query Anda dari atas ke bawah.

Sebagai manusia, kita menulis query dimulai dari SELECT, lalu FROM, kemudian WHERE, dan seterusnya. Namun, bagi mesin database, urutan penulisan (Sintaksis) sangat berbeda dengan urutan eksekusi (Logical Query Processing). Database memiliki alur kerja yang kaku demi efisiensi pemrosesan data.

Perhatikan urutan logis bagaimana database mengeksekusi perintah Anda di balik layar berikut ini:

  1. FROM & JOIN
    Database pertama-tama mendatangi tabel yang dituju dan menggabungkannya jika ada.
  2. WHERE (Penyaring Baris Mentah)
    Di sinilah seleksi pertama terjadi. Database menyaring baris data individual berdasarkan kondisi yang Anda tentukan.
  3. GROUP BY (Pengelompokkan)
    Data yang lolos dari sensor WHERE kemudian dikelompokkan ke dalam grup-grup kecil.
  4. HAVING (Penyaring Kelompok)
    Di sinilah seleksi kedua terjadi. Database menyaring grup-grup yang sudah terbentuk berdasarkan hasil agregasinya.
  5. SELECT
    Database baru memilih kolom mana saja yang ingin ditampilkan ke layar Anda.
  6. ORDER BY & LIMIT
    Terakhir, hasil akhir diurutkan dan dibatasi jumlah barisnya.

Akar Masalah: Garis Waktu GROUP BY

Jika Anda perhatikan urutan di atas, Anda akan melihat sebuah akar aturan yang sangat krusial:

WHERE dieksekusi SEBELUM GROUP BY, sedangkan HAVING dieksekusi SETELAH GROUP BY.

Garis waktu ini adalah hukum alam di dunia SQL yang tidak bisa dinegosiasikan. Dampaknya sangat besar pada aturan penulisan query kita:

  • Mengapa WHERE tidak bisa menggunakan fungsi agregasi (seperti SUM atau AVG)?
    Jawabannya logis: karena saat WHERE bekerja, database bahkan belum melakukan pengelompokkan (GROUP BY). Database belum tahu kelompok mana yang mau dijumlahkan atau dirata-ratakan karena datanya masih berupa baris-baris mentah yang terpisah.
  • Mengapa HAVING bisa menggunakan fungsi agregasi?
    Karena saat HAVING dieksekusi, database sudah selesai mengelompokkan data. Angka total (SUM), rata-rata (AVG), atau jumlah baris (COUNT) sudah matang dan siap disaring.

Memahami Logical Query Processing ini adalah kunci transformasi Anda dari seorang yang sekadar “bisa menulis SQL” menjadi seorang yang “paham cara kerja SQL”. Di bagian berikutnya, kita akan melihat apa yang terjadi jika kita mencoba melanggar hukum alam ini demi memaksakan WHERE diganti dengan HAVING.


3. Bedah Kasus 1: Mengapa WHERE Tidak Bisa Selalu Diganti HAVING?

Setelah memahami urutan eksekusi logis di bagian sebelumnya, Anda mungkin bertanya-tanya: “Jika HAVING berjalan di akhir, bukankah kita bisa memasukkan semua kondisi penyaringan di HAVING saja dan membiarkan WHERE kosong?”

Jawabannya adalah tidak bisa. Memaksakan HAVING untuk melakukan pekerjaan WHERE adalah kesalahan fatal. Di dunia nyata, mencoba menukar fungsi keduanya hanya akan berujung pada dua skenario buruk: Query Anda langsung mogok (Error), atau Query Anda berjalan tapi super lambat (Slow Query).

Mari kita bedah kedua skenario ini dari aspek performa dan logika database.

Skenario A: Query Error (Melanggar Aturan Standar)

Pada sebagian besar database yang ketat terhadap standar ANSI SQL (seperti PostgreSQL, SQL Server, atau Oracle), Anda tidak bisa menyaring kolom biasa menggunakan HAVING jika kolom tersebut tidak masuk ke dalam klausa GROUP BY.

Misalnya, Anda ingin mencari karyawan di departemen ‘IT’. Anda mencoba menulisnya seperti ini:

SQL

-- ❌ SALAH (Akan Menghasilkan Error)
SELECT nama_karyawan, gaji
FROM karyawan
HAVING departemen = 'IT';

Database akan langsung menolak query ini dan mengeluarkan pesan error karena tidak ada klausa GROUP BY departemen. Database bingung mengapa Anda meminta penyaringan kelompok (HAVING) pada data yang tidak pernah dikelompokkan sejak awal.

Skenario B: Query Berhasil, Tapi Merusak Performa (Slow Query)

Beberapa database yang lebih longgar, seperti MySQL atau MariaDB, memang mengizinkan Anda menulis kolom biasa di dalam HAVING meskipun tanpa GROUP BY (selama kolom tersebut ada di bagian SELECT).

SQL

-- ⚠️ BERHASIL JALAN, TAPI SANGAT LAMBAT
SELECT nama_karyawan, departemen
FROM karyawan
HAVING departemen = 'IT';

Secara hasil akhir, query di atas akan menampilkan data yang sama persis dengan jika Anda menggunakan WHERE departemen = 'IT'. Namun, proses di balik layarnya sangat jauh berbeda dan merugikan performa.

Analogi “Berputar Keliling Kota”

Menggunakan HAVING untuk menyaring kolom biasa itu ibarat Anda ingin pergi ke rumah tetangga yang berada persis di sebelah rumah, tetapi Anda memilih untuk berkendara memutari seluruh kota terlebih dahulu, baru kemudian berhenti di sebelah rumah. Sungguh sebuah pemborosan bensin dan waktu, bukan?

Mari kita bandingkan apa yang dilakukan oleh mesin database pada kedua cara tersebut:

  1. Jika Menggunakan WHERE (Efisien)
    Database langsung membuang baris data karyawan non-IT sejak hard disk/penyimpanan awal. Hanya data karyawan IT yang ditarik ke dalam memori kerja database. Jika total karyawan ada 1 juta orang dan karyawan IT hanya 100 orang, database hanya perlu memproses 100 baris data ke tahap berikutnya. Kerja database sangat ringan.
  2. Jika Dipaksa Menggunakan HAVING (Boros Performa)
    Karena HAVING dieksekusi di akhir proses, database terpaksa menarik seluruh 1 juta baris data karyawan dari penyimpanan ke dalam memori kerja. Database membaca nama, gaji, dan departemen semua orang tanpa terkecuali. Setelah semua data termuat di memori, barulah di tahap paling akhir HAVING menyaring dan membuang 999.900 data non-IT tersebut.

Bayangkan jika tabel Anda memiliki puluhan juta baris data. Penggunaan HAVING yang salah tempat seperti ini akan langsung membuat server database Anda “ngos-ngosan”, memori melonjak tinggi, dan aplikasi Anda menjadi sangat lambat (lag).

Golden Rule: Gunakan selalu WHERE untuk menyaring baris data mentah di awal proses, dan sisakan HAVING hanya untuk menyaring hasil kalkulasi kelompok yang memang tidak bisa disaring di awal. Bersikaplah kasihan pada memori server Anda!


4. Bedah Kasus 2: Area Abu-abu & Jebakan Batman

Setelah menguasai perbedaan mendasar antara WHERE dan HAVING, sekarang saatnya kita masuk ke area yang lebih dalam. Di dunia nyata, batasan kedua klausa ini terkadang terlihat kabur. Jika tidak berhati-hati, Anda bisa terjebak dalam “Jebakan Batman” yang membuat query Anda menghasilkan data yang tidak valid bagi laporan bisnis.

Mari kita bedah dua jebakan paling sering ditemui berikut ini.

HAVING Tanpa GROUP BY: Kapan Boleh, Kapan Bad Practice?

Salah satu pertanyaan yang sering membingungkan adalah: “Apakah boleh menulis HAVING tanpa ada klausa GROUP BY di dalam query?” Jawabannya: Boleh secara sintaksis, tetapi perilakunya sangat bergantung pada apa yang Anda saring.

Kapan Ini Boleh Digunakan? (Agregasi Global)

Menulis HAVING tanpa GROUP BY adalah praktik yang sah dan berguna jika Anda ingin menyaring fungsi agregasi untuk seluruh tabel sekaligus. Dalam skenario ini, database akan memperlakukan satu tabel penuh sebagai satu grup besar tunggal.

Contoh Kasus
Anda ingin menampilkan rata-rata gaji seluruh karyawan perusahaan, tetapi hanya jika rata-rata totalnya berada di atas 10 juta rupiah. Jika tidak mencapai angka tersebut, jangan tampilkan apa pun.

SQL

SELECT AVG(gaji) AS rata_gaji_perusahaan
FROM karyawan
HAVING AVG(gaji) > 10000000;

Cara kerjanya
Database menghitung nilai AVG(gaji) dari seluruh baris tabel. Jika hasilnya berstatus TRUE (> 10 juta), maka satu baris hasil akan muncul. Jika FALSE, database mengembalikan hasil kosong (empty set). Ini adalah penggunaan yang valid dan efektif untuk validasi kondisi global.

Kapan Ini Menjadi Bad Practice? (Menyaring Baris Individu)

Seperti yang sempat disinggung di bab sebelumnya, menggunakan HAVING tanpa GROUP BY untuk menyaring kolom biasa (non-agregasi) di database seperti MySQL adalah bad practice yang fatal.

Selain merusak performa karena memaksa database membaca seluruh isi tabel ke memori, query ini juga tidak portabel. Jika suatu hari sistem Anda bermigrasi ke database yang lebih ketat seperti PostgreSQL atau SQL Server, query tersebut akan langsung mogok dan error.

Jebakan NULL: Pembunuh Logika Laporan Bisnis Anda

Ini adalah jebakan paling berbahaya yang sering mengecoh bahkan para pembuat laporan senior sekalipun. Aturan emas yang harus Anda catat adalah: Fungsi agregasi (seperti SUM, AVG, COUNT) di dalam SQL secara otomatis akan mengabaikan (skip) nilai NULL.

Mari kita lihat bagaimana perilaku ini bisa mengacaukan logika laporan bisnis melalui sebuah studi kasus sederhana di lingkungan kampus.

Studi Kasus: Nilai Rata-Rata Mahasiswa Bimbingan

Seorang dosen wali memiliki tiga mahasiswa bimbingan aktif dengan nilai IPK sebagai berikut:

  • Mahasiswa 1: IPK = 4.0
  • Mahasiswa 2: IPK = 3.0
  • Mahasiswa 3: IPK = NULL

Fakultas ingin menyaring dosen wali mana saja yang rata-rata IPK mahasiswa bimbingannya minimal 3.0 menggunakan query berikut:

SQL

SELECT id_dosen, AVG(ipk) AS rata_ipk
FROM mhs
GROUP BY id_dosen
HAVING AVG(ipk) >= 3.0;

Mengapa Ini Menjadi Jebakan?

Secara matematis bisnis, jika ada mahasiswa yang belum memiliki nilai, kita harus berhati-hati dalam mengambil keputusan. Namun, mari kita lihat bagaimana cara database menghitung AVG(ipk) di dalam HAVING:

  1. Karena mahasiswa 3 bernilai NULL, database mengabaikan baris tersebut.
  2. Rumus rata-rata yang dijalankan database adalah: (4.0 + 3.0) / 2 = 3.5
  3. Angka 3.5 lolos dari saringan HAVING >= 3.0. Nama dosen tersebut pun muncul di laporan sebagai dosen dengan performa bimbingan yang aman.

Padahal, realita bisnisnya: Jika Mahasiswa yang bernilai NULL tersebut sebenarnya dianggap bernilai 0 (karena belum berprestasi), maka rata-rata riilnya adalah

(4.0 + 3.0 + 0) / 3 = 2.33

Dosen tersebut seharusnya masuk ke dalam radar evaluasi karena rata-ratanya di bawah 3.0!

Bagaimana Solusinya?

Jika laporan bisnis Anda mengharuskan nilai NULL ikut memengaruhi pembagi data, Anda harus menjinakkan nilai NULL tersebut sejak awal di bagian WHERE menggunakan fungsi seperti COALESCE atau IFNULL sebelum data tersebut menyentuh klausa HAVING.

SQL

-- Mengubah NULL menjadi 0 sebelum dihitung di HAVING
SELECT id_dosen, AVG(COALESCE(ipk, 0)) AS rata_ipk
FROM mhs
GROUP BY id_dosen
HAVING AVG(COALESCE(ipk, 0)) >= 3.0;

Pesan Moral: Jangan pernah mempercayai hasil HAVING pada kolom yang mengizinkan nilai NULL sebelum Anda memastikan bagaimana bisnis Anda ingin memperlakukan data yang kosong tersebut. Terlalu percaya pada HAVING mentah bisa membuat laporan finansial atau performa perusahaan Anda menjadi bias!

5. Contoh Kasus Nyata Industri: Implementasi di Dunia Kerja

Teori tanpa praktik akan terasa hambar. Setelah memahami batasan logika dan jebakan dari WHERE dan HAVING, sekarang mari kita posisikan diri kita sebagai seorang Data Analyst atau Backend Engineer di tiga industri berbeda.

Tiga contoh kasus di bawah ini adalah skenario nyata yang jamak ditemukan dalam operasional perusahaan besar sehari-hari.

Kasus 1 (E-Commerce): Menentukan Target Promo untuk “Pelanggan Setia”

Skenario Bisnis
Tim marketing sebuah platform e-commerce ingin membagikan voucher diskon bernilai besar untuk mempertahankan pelanggan setia (loyal customers). Kriterianya adalah pelanggan yang total belanjanya sudah menembus angka 10 juta rupiah. Namun, Tim Marketing menegaskan bahwa transaksi yang statusnya ‘Gagal’ atau ‘Dibatalkan’ tidak boleh ikut dihitung agar pembagian voucher tepat sasaran.

Di sinilah WHERE dan HAVING harus berkolaborasi erat:

SQL

SELECT id_pelanggan, 
       SUM(total_bayar) AS total_belanja
FROM transaksi
WHERE status_bayar = 'Lunas'        -- 1. FILTER BARIS MENTAH
GROUP BY id_pelanggan               -- 2. KELOMPOKKAN DATA
HAVING SUM(total_bayar) > 10000000; -- 3. FILTER HASIL AGREGASI

Analisis Logika:

  • WHERE status_bayar = 'Lunas'
    Berfungsi mengeliminasi semua transaksi yang batal atau menggantung sejak awal. Jika Anda tidak menyaringnya di WHERE, pelanggan yang memiliki banyak transaksi fiktif/batal bisa ikut terhitung sebagai “pelanggan setia”.
  • HAVING SUM(total_bayar) > 10000000
    Berfungsi menyaring hasil penjumlahan kelompok. Kita tidak bisa menaruh kondisi ini di WHERE karena database harus mengelompokkan dan menjumlahkan seluruh belanjaan per pelanggan terlebih dahulu sebelum tahu siapa saja yang menembus angka 10 juta.

Kasus 2 (Transportasi Online): Sistem Deteksi Fraud Driver (Order Fiktif)

Skenario Bisnis
Tim Risk & Security di sebuah perusahaan ojek online mendeteksi adanya indikasi kecurangan berupa order fiktif yang dilakukan oleh oknum mitra pengemudi (driver). Pola kecurangannya adalah: Driver menyelesaikan banyak orderan dalam satu hari, tetapi total pendapatannya sangat kecil (jarak perjalanan sangat pendek dan tidak wajar). Tim menetapkan radar fraud pada driver yang menyelesaikan lebih dari 5 orderan sehari, tetapi total pendapatan hariannya di bawah Rp50.000.

Query yang digunakan untuk menarik data indikasi kecurangan ini adalah:

SQL

SELECT id_driver, 
       COUNT(id_order) AS total_order, 
       SUM(pendapatan) AS total_pendapatan
FROM orderan
WHERE status_order = 'Selesai'     -- 1. Hanya hitung orderan valid
GROUP BY id_driver, tanggal_order  -- 2. Kelompokkan per driver per hari
HAVING COUNT(id_order) > 5 
   AND SUM(pendapatan) < 50000;    -- 3. Saring yang memenuhi indikasi fraud

Analisis Logika:

  • WHERE: Memastikan database hanya mengambil orderan yang berstatus ‘Selesai’. Kita harus membuang orderan yang dibatalkan oleh sistem atau konsumen agar tidak merusak metrik penilaian performa harian driver.
  • HAVING: Menyaring hasil performa kelompok harian si driver. Klausa HAVING di sini memegang peranan vital karena ia memeriksa dua fungsi agregasi sekaligus (COUNT untuk jumlah orderan dan SUM untuk total uang) untuk menentukan apakah akun driver tersebut layak dibekukan sementara demi investigasi lanjut.

Kasus 3 (HR & Finance): Analisis Alokasi Anggaran Gaji Departemen

Skenario Bisnis: Direktur Keuangan (CFO) sedang meninjau efisiensi anggaran belanja pegawai. Beliau ingin memetakan departemen mana saja yang rata-rata gaji karyawannya sudah melebihi Rp15.000.000 per bulan. Namun, untuk mendapatkan angka rata-rata yang objektif, CFO meminta agar gaji jajaran Direksi (C-Level) tidak diikutkan dalam perhitungan karena angka gaji mereka yang sangat tinggi bisa menimbulkan bias (membuat rata-rata departemen terlihat tinggi, padahal gaji staf biasa masih kecil).

Berikut adalah query yang memecahkan kebutuhan tersebut:

SQL

SELECT departemen, 
       AVG(gaji) AS rata_rata_gaji
FROM karyawan
WHERE posisi != 'C-Level'     -- 1. Singkirkan data Direksi di awal
GROUP BY departemen           -- 2. Kelompokkan berdasarkan Departemen
HAVING AVG(gaji) > 15000000;  -- 3. Tampilkan departemen yang melampaui batas

Analisis Logika:

  • WHERE posisi != 'C-Level'
    Langkah ini wajib dilakukan di awal proses untuk mengeluarkan data Direksi dari mesin hitung database. Jika Anda lupa memasukkan kondisi ini ke dalam WHERE, departemen seperti “Pemasaran” atau “Teknologi” akan memiliki rata-rata gaji yang melambung tinggi hanya karena ada Chief Marketing Officer (CMO) atau Chief Technology Officer (CTO) yang dihitung di dalamnya.
  • HAVING AVG(gaji) > 15000000
    Setelah rata-rata bersih dari bias Direksi berhasil dihitung per departemen, barulah HAVING menyaring departemen mana saja yang secara reguler memakan anggaran besar untuk dilaporkan kepada CFO.

Melalui ketiga studi kasus di atas, Anda sekarang bisa melihat benang merah yang jelas: di dunia industri, WHERE digunakan untuk menentukan subjek data mentah yang valid, sedangkan HAVING digunakan untuk menentukan keputusan bisnis atau kriteria performa. Keduanya bukan musuh yang saling menggantikan, melainkan rekan kerja yang saling melengkapi!


6. Penutup

Kita telah melakukan perjalanan panjang—mulai dari membedah urutan eksekusi logis database, membongkar masalah performa, mewaspadai jebakan NULL, hingga melihat implementasi nyatanya di berbagai industri.

Sebagai rangkuman agar Anda tidak perlu membaca ulang seluruh artikel ini saat sedang dikejar deadline pekerjaan, berikut adalah tabel perbandingan cepat yang bisa Anda jadikan acuan:

Tabel Perbandingan Cepat: WHERE vs HAVING

Fitur / KarakteristikKlausa WHEREKlausa HAVING
Kapan Dieksekusi?Sebelum data dikelompokkan (GROUP BY)Setelah data dikelompokkan (GROUP BY)
Objek PenyaringanBaris data individual / data mentahKelompok atau grup data hasil agregasi
Fungsi Agregasi?Sangat Dilarang (Akan menyebabkan error seperti SUM, COUNT, AVG)Sangat Diizinkan (Memang dirancang khusus untuk ini)
Dampak PerformaSangat efisien karena membuang data tidak valid di awalBisa lambat jika dipaksa menyaring kolom non-agregasi
Kesesuaian StandarStandar di semua jenis databaseDi database ketat, wajib bersama GROUP BY/Agregasi

Tiga Poin Mental Checklist untuk Query Masa Depan Anda

Sebelum Anda menekan tombol Execute atau melakukan Commit pada kode SQL Anda berikutnya, luangkan waktu 5 detik untuk melakukan mental checklist sederhana ini:

  1. “Apakah kondisi ini bisa dinilai tanpa perlu melihat baris lain?”
    Jika Anda menyaring berdasarkan status (misal: status = 'Lunas'), tanggal, atau kategori produk, gunakan WHERE. Jangan tunda penyaringan ini hingga klausa HAVING.
  2. “Apakah kondisi ini melibatkan kata ‘Total’, ‘Rata-rata’, atau ‘Jumlah Keseluruhan’?”
    Jika Anda menyaring kelompok berdasarkan kalkulasi matematika (misal: mencari departemen yang total anggarannya sekian, atau driver yang jumlah orderannya sekian), maka senjata wajib Anda adalah HAVING.
  3. “Apakah ada nilai NULL di kolom yang sedang saya hitung?”
    Ingat selalu bahwa HAVING akan mengabaikan nilai NULL. Pastikan Anda sudah menjinakkan data kosong tersebut di bagian WHERE (menggunakan COALESCE atau IS NOT NULL) agar hasil laporan bisnis Anda tidak bias dan tetap akurat.

Menguasai perbedaan WHERE dan HAVING bukan sekadar tentang menulis query yang bisa berjalan tanpa error. Ini adalah tentang menulis query yang cerdas, efisien, portabel, dan menunjukkan bahwa Anda benar-benar memahami bagaimana mesin database bekerja di balik layar.


Daftar Pustaka

Codd, E.F. (March 1972). “Relational Completeness of Data Base Sublanguages” in Computer Sciences. San Jose, California: IBM Research Laboratory.

Connolly, Thomas M., & Begg, Carolyn E. (2015). Database Systems: A Prac­tical Approach to Design, Implementation, and Management. 6th Edition. Essex, England: Pearson Education.

Coronel, C., Steven, M., Crockett, K., & Blewett, C. (2020). Database Principles: Fundamentals of Design, Implementation, and Management. 3rd Edition. Hampshire, United Kingdom: Cengage Learning

0

Tinggalkan Komentar

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Scroll to Top