TL;DR
GROUP BY buat mengelompokkan data berdasarkan kolom tertentu, HAVING buat filter hasil agregasi. WHERE filter sebelum grouping, HAVING filter sesudah.
Pernah bingung gimana caranya ngitung total penjualan per provinsi? Atau pengen tau rata-rata order per kategori produk? Nah, di sinilah GROUP BY jadi penyelamat kamu.
GROUP BY itu perintah SQL yang dipakai buat mengelompokkan baris data berdasarkan satu atau lebih kolom. Setelah dikelompokkan, kamu bisa pakai fungsi agregasi kayak SUM, COUNT, AVG, MAX, atau MIN untuk ngitung sesuatu dari tiap kelompok.
Praktiknya, GROUP BY misahin tumpukan data penjualan berdasarkan provinsi, terus hitung total penjualan masing-masing provinsi itu.
Sebagai Data Analyst, kamu pasti sering banget dapet request kayak gini:
Semua pertanyaan itu butuh GROUP BY. Tanpa GROUP BY, kamu cuma bisa dapet satu angka total dari seluruh data. Tapi dengan GROUP BY, kamu bisa breakdown datanya jadi insight yang lebih meaningful.
Buat tutorial ini, kita pakai dataset penjualan UMKM Indonesia. Bayangin kamu jadi Data Analyst di platform marketplace yang bantu UMKM jualan online.
| id | tanggal | provinsi | kategori | produk | qty | harga_satuan | total |
|---|---|---|---|---|---|---|---|
| 1 | 2024-01-15 | Jawa Barat | Makanan | Keripik Singkong | 50 | 15000 | 750000 |
| 2 | 2024-01-15 | Jawa Tengah | Kerajinan | Batik Tulis | 5 | 250000 | 1250000 |
| 3 | 2024-01-16 | Jawa Barat | Makanan | Dodol Garut | 30 | 25000 | 750000 |
| 4 | 2024-01-16 | Bali | Kerajinan | Ukiran Kayu | 3 | 500000 | 1500000 |
| 5 | 2024-01-17 | Jawa Barat | Fashion | Kaos Distro | 20 | 85000 | 1700000 |
| 6 | 2024-01-17 | Jawa Tengah | Makanan | Lumpia Semarang | 40 | 20000 | 800000 |
| 7 | 2024-01-18 | Bali | Makanan | Pie Susu | 100 | 8000 | 800000 |
| 8 | 2024-01-18 | Yogyakarta | Kerajinan | Perak Kotagede | 10 | 150000 | 1500000 |
| 9 | 2024-01-19 | Jawa Barat | Makanan | Keripik Singkong | 60 | 15000 | 900000 |
| 10 | 2024-01-19 | Jawa Tengah | Fashion | Batik Cap | 15 | 120000 | 1800000 |
| id | nama_usaha | provinsi | kategori | tahun_berdiri |
|---|---|---|---|---|
| 1 | Keripik Bu Siti | Jawa Barat | Makanan | 2018 |
| 2 | Batik Sekar | Jawa Tengah | Kerajinan | 2015 |
| 3 | Dodol Pak Asep | Jawa Barat | Makanan | 2020 |
| 4 | Bali Craft | Bali | Kerajinan | 2019 |
| 5 | Urban Threads | Jawa Barat | Fashion | 2021 |
Ini struktur dasar GROUP BY:
SELECT kolom_grouping, FUNGSI_AGREGASI(kolom)
FROM nama_tabel
WHERE kondisi
GROUP BY kolom_grouping;
Yang perlu kamu inget:
1. Kolom yang muncul di SELECT (yang bukan fungsi agregasi) HARUS ada di GROUP BY
2. GROUP BY dijalankan SETELAH WHERE
3. Kamu bisa GROUP BY lebih dari satu kolom
Ini contoh paling basic. Kita mau tau total penjualan di tiap provinsi.
SELECT
provinsi,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY provinsi;
Hasil:
| provinsi | total_penjualan |
|---|---|
| Jawa Barat | 4100000 |
| Jawa Tengah | 3850000 |
| Bali | 2300000 |
| Yogyakarta | 1500000 |
Keren kan? Dengan satu query, kamu bisa langsung liat provinsi mana yang penjualannya paling gede.
Sekarang kita mau liat performa tiap kategori produk.
SELECT
kategori,
COUNT(*) AS jumlah_transaksi,
SUM(total) AS total_penjualan,
AVG(total) AS rata_rata_order
FROM penjualan
GROUP BY kategori;
Hasil:
| kategori | jumlah_transaksi | total_penjualan | rata_rata_order |
|---|---|---|---|
| Makanan | 5 | 4000000 | 800000 |
| Kerajinan | 3 | 4250000 | 1416667 |
| Fashion | 2 | 3500000 | 1750000 |
Dari sini keliatan bahwa walaupun Makanan punya transaksi paling banyak, tapi rata-rata nilai ordernya paling kecil. Sementara Fashion punya rata-rata order tertinggi.
Kamu juga bisa grouping berdasarkan lebih dari satu kolom. Misalnya, kita mau liat penjualan per provinsi DAN per kategori.
SELECT
provinsi,
kategori,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY provinsi, kategori
ORDER BY provinsi, total_penjualan DESC;
Hasil:
| provinsi | kategori | total_penjualan |
|---|---|---|
| Bali | Kerajinan | 1500000 |
| Bali | Makanan | 800000 |
| Jawa Barat | Fashion | 1700000 |
| Jawa Barat | Makanan | 2400000 |
| Jawa Tengah | Fashion | 1800000 |
| Jawa Tengah | Kerajinan | 1250000 |
| Jawa Tengah | Makanan | 800000 |
| Yogyakarta | Kerajinan | 1500000 |
Nah, sekarang kamu bisa liat breakdown yang lebih detail. Jawa Barat ternyata paling kuat di kategori Makanan.
Oke, sekarang bayangin kamu mau filter hasil GROUP BY. Misalnya, kamu cuma mau liat provinsi yang total penjualannya di atas 2 juta.
Banyak yang mikir, "Ya udah pake WHERE aja dong!"
Tapi tunggu dulu. WHERE itu filter SEBELUM data di-group. Jadi kamu ga bisa nulis kayak gini:
-- INI SALAH!
SELECT provinsi, SUM(total) AS total_penjualan
FROM penjualan
WHERE SUM(total) > 2000000 -- Error!
GROUP BY provinsi;
Query di atas bakal error karena WHERE ga bisa pake fungsi agregasi. Di sinilah HAVING masuk.
HAVING itu filter yang dijalankan SETELAH GROUP BY. Jadi kamu bisa filter berdasarkan hasil agregasi.
SELECT kolom_grouping, FUNGSI_AGREGASI(kolom)
FROM nama_tabel
WHERE kondisi_per_baris
GROUP BY kolom_grouping
HAVING kondisi_agregasi;
Urutan eksekusi:
1. FROM - ambil data dari tabel
2. WHERE - filter baris individual
3. GROUP BY - kelompokkan data
4. HAVING - filter hasil grouping
5. SELECT - pilih kolom yang mau ditampilkan
6. ORDER BY - urutkan hasil
Sekarang kita coba filter provinsi dengan total penjualan di atas 2 juta.
SELECT
provinsi,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY provinsi
HAVING SUM(total) > 2000000;
Hasil:
| provinsi | total_penjualan |
|---|---|
| Jawa Barat | 4100000 |
| Jawa Tengah | 3850000 |
| Bali | 2300000 |
Yogyakarta ga masuk karena total penjualannya cuma 1.5 juta.
Ini yang sering bikin bingung. Kapan pake WHERE, kapan pake HAVING?
Inget aturan ini:
- WHERE: filter baris SEBELUM grouping (filter per record)
- HAVING: filter SETELAH grouping (filter hasil agregasi)
Misalnya, kamu mau liat total penjualan per kategori, tapi:
1. Cuma untuk transaksi di bulan Januari 2024
2. Dan cuma kategori yang total penjualannya di atas 1 juta
SELECT
kategori,
SUM(total) AS total_penjualan
FROM penjualan
WHERE tanggal BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY kategori
HAVING SUM(total) > 1000000;
Hasil:
| kategori | total_penjualan |
|---|---|
| Makanan | 4000000 |
| Kerajinan | 4250000 |
| Fashion | 3500000 |
Di sini, WHERE filter dulu transaksi yang di Januari, baru di-group dan difilter pake HAVING.
HAVING juga sering dipake bareng COUNT. Misalnya, kamu mau cari provinsi yang punya minimal 3 transaksi.
SELECT
provinsi,
COUNT(*) AS jumlah_transaksi
FROM penjualan
GROUP BY provinsi
HAVING COUNT(*) >= 3;
Hasil:
| provinsi | jumlah_transaksi |
|---|---|
| Jawa Barat | 4 |
| Jawa Tengah | 3 |
Bali dan Yogyakarta ga masuk karena transaksinya kurang dari 3.
Kamu bisa pakai AND dan OR di HAVING, sama kayak di WHERE.
SELECT
kategori,
COUNT(*) AS jumlah_transaksi,
SUM(total) AS total_penjualan,
AVG(total) AS rata_rata
FROM penjualan
GROUP BY kategori
HAVING COUNT(*) >= 2 AND AVG(total) > 1000000;
Hasil:
| kategori | jumlah_transaksi | total_penjualan | rata_rata |
|---|---|---|---|
| Kerajinan | 3 | 4250000 | 1416667 |
| Fashion | 2 | 3500000 | 1750000 |
Query ini cari kategori yang punya minimal 2 transaksi DAN rata-rata order di atas 1 juta.
Ini sering banget dipake buat bikin laporan periodik. Misalnya, total penjualan per minggu.
SELECT
DATE_TRUNC('week', tanggal) AS minggu,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY DATE_TRUNC('week', tanggal)
ORDER BY minggu;
Atau kalau kamu mau per bulan:
SELECT
EXTRACT(YEAR FROM tanggal) AS tahun,
EXTRACT(MONTH FROM tanggal) AS bulan,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY
EXTRACT(YEAR FROM tanggal),
EXTRACT(MONTH FROM tanggal)
ORDER BY tahun, bulan;
Sekarang kita gabungin tabel penjualan dengan tabel umkm. Misalnya, kita mau tau total penjualan per tahun berdiri UMKM.
SELECT
u.tahun_berdiri,
COUNT(DISTINCT u.id) AS jumlah_umkm,
SUM(p.total) AS total_penjualan
FROM penjualan p
JOIN umkm u ON p.provinsi = u.provinsi AND p.kategori = u.kategori
GROUP BY u.tahun_berdiri
ORDER BY u.tahun_berdiri;
Ini bisa ngasih insight menarik, misalnya UMKM yang udah lama berdiri penjualannya gimana dibanding yang baru.
-- SALAH
SELECT provinsi, kategori, SUM(total)
FROM penjualan
GROUP BY provinsi; -- kategori ga di-include
Ini bakal error. Semua kolom yang bukan fungsi agregasi HARUS ada di GROUP BY.
-- SALAH
SELECT provinsi, SUM(total)
FROM penjualan
WHERE SUM(total) > 1000000 -- Error!
GROUP BY provinsi;
Pake HAVING, bukan WHERE, untuk filter hasil agregasi.
-- SALAH
SELECT provinsi, SUM(total)
FROM penjualan
HAVING SUM(total) > 1000000
GROUP BY provinsi; -- HAVING sebelum GROUP BY
Urutan yang bener: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY.
NULL itu dikelompokkan jadi satu grup tersendiri. Kadang ini bikin hasil query jadi unexpected. Pake COALESCE kalau perlu handle NULL.
SELECT
COALESCE(provinsi, 'Tidak Diketahui') AS provinsi,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY provinsi;
Selalu kasih alias yang descriptive ke hasil agregasi. Ini bikin query lebih readable dan hasilnya lebih gampang dipahami.
SELECT
kategori,
SUM(total) AS total_pendapatan,
AVG(total) AS rata_rata_transaksi,
MAX(total) AS transaksi_terbesar
FROM penjualan
GROUP BY kategori;
Hasil GROUP BY biasanya lebih berguna kalau di-sort.
SELECT
provinsi,
SUM(total) AS total_penjualan
FROM penjualan
GROUP BY provinsi
ORDER BY total_penjualan DESC; -- Dari yang terbesar
Rata-rata sering menghasilkan angka decimal panjang. Pake ROUND biar lebih clean.
SELECT
kategori,
ROUND(AVG(total), 2) AS rata_rata
FROM penjualan
GROUP BY kategori;
Kamu bisa bikin grouping yang lebih custom pake CASE WHEN.
SELECT
CASE
WHEN total < 1000000 THEN 'Small'
WHEN total < 2000000 THEN 'Medium'
ELSE 'Large'
END AS order_size,
COUNT(*) AS jumlah
FROM penjualan
GROUP BY
CASE
WHEN total < 1000000 THEN 'Small'
WHEN total < 2000000 THEN 'Medium'
ELSE 'Large'
END;
Kadang ada yang bingung, mending pake GROUP BY atau subquery. Aturan simpelnya:
Contoh pake subquery:
-- Cari transaksi yang nilainya di atas rata-rata
SELECT *
FROM penjualan
WHERE total > (
SELECT AVG(total) FROM penjualan
);
Nah, sekarang kamu udah paham kan gimana caranya pake GROUP BY dan HAVING? Inget poin-poin ini:
Latihan itu kunci. Coba bikin query sendiri pake dataset yang kamu punya. Mulai dari yang simple kayak COUNT dan SUM per kategori, terus lanjut ke yang lebih kompleks.
Kalau ada pertanyaan, langsung aja praktek di nguliksql.id.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Cara pakai DATE_TRUNC di SQL buat memotong timestamp jadi awal bulan, minggu, atau jam. Kunci bikin laporan penjualan per periode yang rapi.
Cara mengisi tanggal kosong di time series SQL pakai generate_series dan LEFT JOIN biar grafik dan laporan gak bolong. Contoh penjualan harian UMKM.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.