sqljoingroup-byMahir4 mnt baca

JOIN dan Agregasi

Gabungkan tabel dengan JOIN, rangkum dengan GROUP BY dan fungsi agregasi.

JOIN itu seperti mencocokkan daftar hadir

Bayangkan kamu punya dua daftar: daftar nama siswa dan daftar nilai ujian. Keduanya punya kolom NIS yang sama. JOIN itu seperti menempelkan nilai ke nama yang NIS-nya cocok, sehingga jadi satu daftar lengkap. Di database nyata, data memang sengaja dipecah ke banyak tabel (pelanggan, produk, transaksi) supaya tidak ada duplikasi, lalu disambung lagi dengan JOIN saat dibutuhkan.

Kenapa ini jantungnya kerja analyst

Hampir tidak ada pertanyaan bisnis yang bisa dijawab dari satu tabel saja. "Produk kategori apa yang paling laku di kalangan pelanggan Jakarta?" butuh tabel pelanggan (kota), produk (kategori), dan transaksi (jumlah). Analyst yang tidak bisa JOIN akan mentok di pertanyaan paling dasar. Sebaliknya, JOIN yang salah lebih berbahaya daripada tidak bisa JOIN: angkanya terlihat meyakinkan padahal salah, lalu dipakai untuk keputusan bisnis. Jadi kuasai ini baik-baik.

Contoh minimal: dua tabel kecil

python
import sqlite3
con = sqlite3.connect(":memory:")
cur = con.cursor()
cur.execute("CREATE TABLE pelanggan (id INTEGER, nama TEXT)")
cur.execute("CREATE TABLE transaksi (id INTEGER, id_pelanggan INTEGER, total INTEGER)")
cur.execute("INSERT INTO pelanggan VALUES (1, 'Budi'), (2, 'Sari')")
cur.execute("INSERT INTO transaksi VALUES (101, 1, 50000), (102, 1, 75000)")
query = """
SELECT p.nama, t.total
FROM pelanggan p
JOIN transaksi t ON t.id_pelanggan = p.id
"""
for row in cur.execute(query):
    print(row)
# ('Budi', 50000)
# ('Budi', 75000)
con.close()

Perhatikan Sari tidak muncul: INNER JOIN (JOIN biasa) hanya menampilkan baris yang punya pasangan di kedua tabel. Kalau kamu butuh semua pelanggan termasuk yang belum pernah transaksi, pakai LEFT JOIN:

sql
SELECT p.nama, COUNT(t.id) AS jumlah_transaksi
FROM pelanggan p
LEFT JOIN transaksi t ON t.id_pelanggan = p.id
GROUP BY p.nama;

Sari akan muncul dengan jumlah_transaksi 0. Pilih jenis JOIN sesuai pertanyaan: "yang bertransaksi" berarti INNER, "semua pelanggan" berarti LEFT.

Cara membaca query JOIN yang panjang

Query dengan banyak JOIN terlihat menakutkan, tapi ada trik membacanya: mulai dari klausa FROM, bukan SELECT. FROM memberi tahu tabel utama dan urutan penyambungannya, ON memberi tahu kunci penghubungnya, dan SELECT di akhir hanya memilih kolom mana yang ditampilkan. Alias pendek seperti pj, pl, pr bukan sekadar gaya: tanpa alias, query tiga tabel jadi susah dibaca karena nama tabelnya diulang terus. Kebiasaan baik: alias selalu singkat dan konsisten di seluruh query.

Contoh realistis: omzet per kategori

Skema toko: pelanggan(id, nama, kota), produk(id, nama, kategori, harga), penjualan(id, id_pelanggan, id_produk, jumlah). Pertanyaan: kategori apa yang menghasilkan omzet terbesar, hanya dari pelanggan Jakarta?

sql
SELECT pr.kategori,
       SUM(pj.jumlah * pr.harga) AS omzet
FROM penjualan pj
JOIN pelanggan pl ON pj.id_pelanggan = pl.id
JOIN produk pr ON pj.id_produk = pr.id
WHERE pl.kota = 'Jakarta'
GROUP BY pr.kategori
HAVING SUM(pj.jumlah * pr.harga) > 1000000
ORDER BY omzet DESC;

Alurnya: sambungkan tiga tabel dulu, saring kota dengan WHERE, kelompokkan per kategori dengan GROUP BY, saring hasil kelompok dengan HAVING, urutkan. Fungsi agregasi yang wajib hafal: COUNT, SUM, AVG, MIN, MAX.

Jebakan 1: lupa kondisi ON

SALAH:

sql
SELECT p.nama, t.total
FROM pelanggan p
JOIN transaksi t;  -- tidak ada ON!

BENAR:

sql
SELECT p.nama, t.total
FROM pelanggan p
JOIN transaksi t ON t.id_pelanggan = p.id;

Tanpa ON, database memasangkan setiap baris tabel kiri dengan setiap baris tabel kanan (cartesian product). Tabel 1.000 x 1.000 baris meledak jadi 1 juta baris, dan angkanya jadi sampah. Kebiasaan aman: setiap selesai menulis JOIN, langsung tulis ON-nya sebelum lanjut, lalu cek apakah jumlah baris hasilnya masuk akal.

Jebakan 2: WHERE untuk menyaring hasil agregasi

SALAH:

sql
SELECT kota, COUNT(*) AS jml
FROM pelanggan
WHERE jml > 10      -- jml belum ada saat WHERE dieksekusi!
GROUP BY kota;

BENAR:

sql
SELECT kota, COUNT(*) AS jml
FROM pelanggan
GROUP BY kota
HAVING COUNT(*) > 10;

Aturannya: WHERE menyaring baris sebelum dikelompokkan, HAVING menyaring grup sesudah dikelompokkan. Alias kolom hasil agregasi (seperti jml) tidak bisa dipakai di WHERE karena WHERE berjalan duluan.

Catatan teknis: COUNT(*) menghitung semua baris termasuk yang NULL, sedangkan COUNT(kolom) melewatkan NULL. Untuk "berapa pelanggan yang punya nomor HP", pakai COUNT(no_hp), bukan COUNT(*). Dan ingat padanan pandas-nya: WHERE itu df[mask], GROUP BY itu groupby(), JOIN itu merge(..., how=...). Bisa satu, belajar yang lain jadi jauh lebih cepat.

Tantangan

Omzet per kategori

Tabel transaksi(id_produk, jumlah) dan produk(id, nama, kategori, harga). Tulis query: total omzet (jumlah*harga) per kategori, hanya kategori dengan omzet > 1000000, urut menurun.