Satu angka untuk seluruh tabel menyembunyikan perbedaan yang justru paling penting: pelanggan Jakarta mungkin berperilaku berbeda dari pelanggan Bandung, dan kategori elektronik punya profil harga berbeda dari fashion. GROUP BY memecah tabel menjadi kelompok-kelompok agar fungsi agregasi dari chapter Agregasi Data dihitung terpisah untuk tiap kelompok, sementara HAVING menyaring kelompok hasilnya. Chapter ini juga membedah urutan eksekusi logis query — kunci untuk memahami mengapa beberapa query ditolak database.

Mengelompokkan Baris dengan GROUP BY #

Klausa GROUP BY mengumpulkan baris dengan nilai kolom yang sama menjadi satu kelompok, lalu setiap fungsi agregasi di SELECT dihitung per kelompok:

sql
SELECT kota, COUNT(*) AS jumlah_pelanggan
FROM pelanggan
GROUP BY kota

Output yang Dihasilkan:

text
(None, 1)
('Bandung', 2)
('Jakarta', 2)
('Surabaya', 1)

Mari kita telaah query di atas: GROUP BY kota membentuk empat kelompok (Bandung, Jakarta, Surabaya, dan satu kelompok NULL), COUNT(*) menghitung anggota tiap kelompok, dan kolom kota pada SELECT menampilkan pengenal kelompok. Perhatikan baris pertama: NULL membentuk kelompok tersendiri karena “tidak bernilai” dianggap satu kategori sama — pola yang sering mengejutkan saat data profil tidak lengkap. Aturan mainnya: setiap kolom pada SELECT harus berupa kolom pengelompokan atau berada dalam fungsi agregasi.

GROUP BY Multi Kolom #

Pengelompokan boleh memakai lebih dari satu kolom; setiap kombinasi nilai unik menjadi satu kelompok. Query berikut menghitung jumlah pesanan per pelanggan per status:

sql
SELECT pelanggan_id, status, COUNT(*) AS jumlah_pesanan
FROM pesanan
GROUP BY pelanggan_id, status

Output yang Dihasilkan:

text
(1, 'dikirim', 1)
(1, 'selesai', 2)
(2, 'diproses', 1)
(2, 'selesai', 1)
(3, 'diproses', 1)
(3, 'selesai', 1)
(4, 'dibatalkan', 1)
(5, 'selesai', 1)

Pelanggan 1 (Budi) pecah menjadi dua kelompok: satu pesanan dikirim dan dua pesanan selesai. Pola seperti ini menjadi menarik pada data nyata — misalnya menemukan pelanggan yang sering membatalkan pesanan cukup dengan menyaring kelompok dibatalkan yang jumlahnya besar.

Menyaring Kelompok dengan HAVING #

Bagaimana jika kita hanya ingin pesanan dengan total belanja di atas satu juta rupiah? Total belanja adalah hasil SUM, dan WHERE tidak bisa menyaring hasil agregasi karena WHERE berjalan sebelum pengelompokan. Tugas tersebut jatuh kepada HAVING:

sql
SELECT pesanan_id, SUM(subtotal) AS total
FROM detail_pesanan
GROUP BY pesanan_id
HAVING SUM(subtotal) > 1000000

Output yang Dihasilkan:

text
(1, 4000000)
(2, 1130000)
(3, 12500000)
(4, 1150000)
(6, 1500000)

Tanpa HAVING, query tersebut mengembalikan delapan kelompok; filter SUM(subtotal) > 1000000 menyisakan lima pesanan bernilai besar — pesanan 7 (900 ribu) dan pesanan 9 (250 ribu) tersaring, sedangkan pesanan 5 tidak lolos karena SUM atas satu-satunya barisnya bernilai NULL. Perbedaan peran kedua klausa penyaring dirangkum berikut:

AspekWHEREHAVING
Menyaringbaris satu per satukelompok hasil agregasi
Bisa memakai fungsi agregasitidakya
Berjalan pada tahapsebelum pengelompokansetelah pengelompokan

Kedua klausa juga boleh hadir bersama. Query berikut menghitung ulang nilai pesanan setelah membuang baris subtotal NULL, menyaring kelompok besar, lalu mengurutkan hasilnya:

sql
SELECT pesanan_id, SUM(subtotal) AS total
FROM detail_pesanan
WHERE subtotal IS NOT NULL
GROUP BY pesanan_id
HAVING SUM(subtotal) > 1000000
ORDER BY total DESC

Output yang Dihasilkan:

text
(3, 12500000)
(1, 4000000)
(6, 1500000)
(4, 1150000)
(2, 1130000)

Urutan Eksekusi Logis Query #

Query ditulis dengan SELECT di depan, tetapi database memproses klausanya dalam urutan berbeda:

Alur tersebut menjelaskan tiga perilaku yang sering membingungkan pemula. Pertama, WHERE tidak mengenal alias dari SELECT — misalnya WHERE total > 1000000 gagal karena kolom total baru lahir di tahap SELECT, jauh setelah WHERE selesai. Kedua, ORDER BY justru mengenal alias tersebut karena berjalan paling akhir — pada query terakhir, ORDER BY total DESC sah digunakan. Ketiga, HAVING melihat hasil GROUP BY sehingga bisa menyaring SUM(subtotal).

Semua query pada chapter ini masih membaca satu tabel dalam satu waktu, padahal nilai pesanan 3 terbesar justru menarik untuk dihubungkan dengan nama pelanggan dan nama produknya. Penggabungan data antar tabel adalah tugas chapter JOIN.