TL;DR
JOIN itu buat gabungin data dari 2+ tabel. INNER JOIN ambil yang match aja, LEFT JOIN ambil semua dari tabel kiri, RIGHT JOIN ambil semua dari tabel kanan, FULL JOIN ambil semuanya. Pilih sesuai kebutuhan analisis kamu.
Pernah bingung kenapa hasil query kamu returnnya lebih banyak atau lebih sedikit dari yang diharapkan? Atau mungkin pernah dapet error "ambiguous column name" yang bikin frustrasi?
Tenang, kamu ga sendirian kok. JOIN emang salah satu konsep SQL yang paling sering bikin pemula garuk-garuk kepala. Tapi setelah paham logikanya, JOIN justru jadi skill yang bakal sering banget kamu pakai sehari-hari.
Di banyak startup, data karyawan disimpan di satu tabel, data departemen di tabel lain. Kalau mau tau "siapa aja yang kerja di departemen Engineering?", kamu perlu gabungin kedua tabel itu.
Nah, JOIN itu cara SQL buat menggabungkan data dari dua atau lebih tabel berdasarkan kolom yang nyambung (biasanya foreign key).
Contoh situasi yang butuh JOIN:
- Gabungin data order dengan data customer
- Lihat produk beserta nama kategorinya
- Tampilkan karyawan beserta nama departemennya
- Analisis transaksi dengan info user
Intinya, kalau data kamu tersebar di beberapa tabel (yang emang best practice dalam database design), JOIN itu wajib dikuasai.
Biar gampang dipahami, kita pakai contoh dataset startup di Jakarta. Ada dua tabel: karyawan dan departemen.
Tabel: karyawan
| id | nama | departemen_id | gaji |
|---|---|---|---|
| 1 | Budi Santoso | 1 | 15000000 |
| 2 | Siti Rahayu | 2 | 12000000 |
| 3 | Andi Wijaya | 1 | 18000000 |
| 4 | Dewi Lestari | 3 | 14000000 |
| 5 | Rudi Hartono | NULL | 10000000 |
Tabel: departemen
| id | nama_dept | lokasi |
|---|---|---|
| 1 | Engineering | Sudirman |
| 2 | Marketing | Kuningan |
| 3 | Finance | Sudirman |
| 4 | HR | Kuningan |
Perhatiin nih:
- Rudi Hartono punya departemen_id NULL (belum di-assign)
- Departemen HR (id=4) ga punya karyawan
Kondisi kayak gini sering banget terjadi di real world. Dan di sinilah pemahaman tentang jenis-jenis JOIN jadi penting.
INNER JOIN itu yang paling basic dan paling sering dipakai. Konsepnya simpel: cuma ambil data yang ada di KEDUA tabel.
Karyawan Departemen
┌─────┐ ┌─────┐
│ │ │ │
│ ███████████████████████████████│
│ │ │ │
└─────┘ └─────┘
↑
Bagian yang di-highlight
= hasil INNER JOIN
SELECT kolom1, kolom2, ...
FROM tabel_kiri
INNER JOIN tabel_kanan ON tabel_kiri.kolom = tabel_kanan.kolom;
SELECT
k.nama,
k.gaji,
d.nama_dept,
d.lokasi
FROM karyawan k
INNER JOIN departemen d ON k.departemen_id = d.id;
Output:
| nama | gaji | nama_dept | lokasi |
|---|---|---|---|
| Budi Santoso | 15000000 | Engineering | Sudirman |
| Siti Rahayu | 12000000 | Marketing | Kuningan |
| Andi Wijaya | 18000000 | Engineering | Sudirman |
| Dewi Lestari | 14000000 | Finance | Sudirman |
Perhatiin:
- Rudi Hartono ga muncul (karena departemen_id NULL, ga match sama departemen manapun)
- Departemen HR ga muncul (karena ga ada karyawan yang punya departemen_id = 4)
SELECT
k.nama,
k.gaji,
d.nama_dept
FROM karyawan k
INNER JOIN departemen d ON k.departemen_id = d.id
WHERE d.lokasi = 'Sudirman';
Output:
| nama | gaji | nama_dept |
|---|---|---|
| Budi Santoso | 15000000 | Engineering |
| Andi Wijaya | 18000000 | Engineering |
| Dewi Lestari | 14000000 | Finance |
SELECT
d.nama_dept,
COUNT(k.id) AS jumlah_karyawan,
AVG(k.gaji) AS rata_rata_gaji
FROM karyawan k
INNER JOIN departemen d ON k.departemen_id = d.id
GROUP BY d.nama_dept;
Output:
| nama_dept | jumlah_karyawan | rata_rata_gaji |
|---|---|---|
| Engineering | 2 | 16500000 |
| Marketing | 1 | 12000000 |
| Finance | 1 | 14000000 |
Kapan Pakai INNER JOIN?
- Kamu cuma butuh data yang punya relasi lengkap di kedua tabel
- Ga peduli sama data yang "yatim" (orphan records)
- Contoh: Laporan penjualan yang cuma nampilin order yang udah ada customer-nya
LEFT JOIN (atau LEFT OUTER JOIN) itu ambil semua data dari tabel kiri, plus data yang match dari tabel kanan. Kalau ga match, kolom dari tabel kanan jadi NULL.
Karyawan Departemen
┌─────┐ ┌─────┐
│█████│ │ │
│█████████████████████████████│
│█████│ │ │
└─────┘ └─────┘
↑
Semua dari kiri + yang match dari kanan
SELECT kolom1, kolom2, ...
FROM tabel_kiri
LEFT JOIN tabel_kanan ON tabel_kiri.kolom = tabel_kanan.kolom;
SELECT
k.nama,
k.gaji,
d.nama_dept,
d.lokasi
FROM karyawan k
LEFT JOIN departemen d ON k.departemen_id = d.id;
Output:
| nama | gaji | nama_dept | lokasi |
|---|---|---|---|
| Budi Santoso | 15000000 | Engineering | Sudirman |
| Siti Rahayu | 12000000 | Marketing | Kuningan |
| Andi Wijaya | 18000000 | Engineering | Sudirman |
| Dewi Lestari | 14000000 | Finance | Sudirman |
| Rudi Hartono | 10000000 | NULL | NULL |
Nah, sekarang Rudi Hartono muncul! Tapi nama_dept dan lokasi-nya NULL karena dia ga punya departemen.
Ini pattern yang sering banget dipakai: cari data di tabel kiri yang GA ADA di tabel kanan.
SELECT
k.nama,
k.gaji
FROM karyawan k
LEFT JOIN departemen d ON k.departemen_id = d.id
WHERE d.id IS NULL;
Output:
| nama | gaji |
|---|---|
| Rudi Hartono | 10000000 |
Pattern ini berguna banget buat:
- Cari customer yang belum pernah order
- Cari produk yang belum pernah terjual
- Cari user yang belum lengkapin profile
COALESCE itu buat handle NULL values. Berguna banget biar output lebih readable.
SELECT
k.nama,
k.gaji,
COALESCE(d.nama_dept, 'Belum Ditentukan') AS departemen
FROM karyawan k
LEFT JOIN departemen d ON k.departemen_id = d.id;
Output:
| nama | gaji | departemen |
|---|---|---|
| Budi Santoso | 15000000 | Engineering |
| Siti Rahayu | 12000000 | Marketing |
| Andi Wijaya | 18000000 | Engineering |
| Dewi Lestari | 14000000 | Finance |
| Rudi Hartono | 10000000 | Belum Ditentukan |
Kapan Pakai LEFT JOIN?
- Mau keep semua data dari tabel utama
- Perlu analisis data yang "yatim" (ga punya relasi)
- Contoh: List semua karyawan, termasuk yang belum di-assign ke departemen
RIGHT JOIN kebalikannya LEFT JOIN. Ambil semua data dari tabel kanan, plus data yang match dari tabel kiri.
Karyawan Departemen
┌─────┐ ┌─────┐
│ │ │█████│
│█████████████████████████████│
│ │ │█████│
└─────┘ └─────┘
↑
Yang match dari kiri + semua dari kanan
SELECT kolom1, kolom2, ...
FROM tabel_kiri
RIGHT JOIN tabel_kanan ON tabel_kiri.kolom = tabel_kanan.kolom;
SELECT
k.nama,
k.gaji,
d.nama_dept,
d.lokasi
FROM karyawan k
RIGHT JOIN departemen d ON k.departemen_id = d.id;
Output:
| nama | gaji | nama_dept | lokasi |
|---|---|---|---|
| Budi Santoso | 15000000 | Engineering | Sudirman |
| Andi Wijaya | 18000000 | Engineering | Sudirman |
| Siti Rahayu | 12000000 | Marketing | Kuningan |
| Dewi Lestari | 14000000 | Finance | Sudirman |
| NULL | NULL | HR | Kuningan |
Sekarang departemen HR muncul! Tapi nama dan gaji-nya NULL karena ga ada karyawan di departemen itu.
SELECT
d.nama_dept,
d.lokasi
FROM karyawan k
RIGHT JOIN departemen d ON k.departemen_id = d.id
WHERE k.id IS NULL;
Output:
| nama_dept | lokasi |
|---|---|
| HR | Kuningan |
Jujur aja nih, dalam prakteknya RIGHT JOIN jarang dipakai. Kenapa? Karena kamu bisa dapetin hasil yang sama dengan nuker posisi tabel dan pakai LEFT JOIN.
Query ini:
SELECT * FROM karyawan k RIGHT JOIN departemen d ON k.departemen_id = d.id;
Sama hasilnya dengan:
SELECT * FROM departemen d LEFT JOIN karyawan k ON d.id = k.departemen_id;
Kebanyakan developer lebih prefer LEFT JOIN karena lebih gampang dibaca (tabel utama selalu di kiri).
FULL OUTER JOIN (atau FULL JOIN) itu gabungan LEFT dan RIGHT JOIN. Ambil semua data dari kedua tabel, match yang bisa di-match, sisanya dikasih NULL.
Karyawan Departemen
┌─────┐ ┌─────┐
│█████│ │█████│
│█████████████████████████████│
│█████│ │█████│
└─────┘ └─────┘
↑ ↑
Semua dari kedua tabel
SELECT kolom1, kolom2, ...
FROM tabel_kiri
FULL OUTER JOIN tabel_kanan ON tabel_kiri.kolom = tabel_kanan.kolom;
SELECT
k.nama,
k.gaji,
d.nama_dept,
d.lokasi
FROM karyawan k
FULL OUTER JOIN departemen d ON k.departemen_id = d.id;
Output:
| nama | gaji | nama_dept | lokasi |
|---|---|---|---|
| Budi Santoso | 15000000 | Engineering | Sudirman |
| Andi Wijaya | 18000000 | Engineering | Sudirman |
| Siti Rahayu | 12000000 | Marketing | Kuningan |
| Dewi Lestari | 14000000 | Finance | Sudirman |
| Rudi Hartono | 10000000 | NULL | NULL |
| NULL | NULL | HR | Kuningan |
Sekarang SEMUA data muncul:
- Karyawan yang punya departemen
- Karyawan yang ga punya departemen (Rudi)
- Departemen yang ga punya karyawan (HR)
Catatan: MySQL ga support FULL OUTER JOIN secara native. Kamu perlu pakai UNION:
SELECT k.nama, k.gaji, d.nama_dept, d.lokasi
FROM karyawan k
LEFT JOIN departemen d ON k.departemen_id = d.id
UNION
SELECT k.nama, k.gaji, d.nama_dept, d.lokasi
FROM karyawan k
RIGHT JOIN departemen d ON k.departemen_id = d.id;
Kapan Pakai FULL OUTER JOIN?
- Mau analisis data yang "yatim" di kedua sisi
- Data reconciliation atau audit
- Contoh: Bandingkan data dari dua sistem yang harusnya sama
CROSS JOIN itu beda sendiri. Dia bikin semua kemungkinan kombinasi dari kedua tabel (Cartesian Product).
SELECT kolom1, kolom2, ...
FROM tabel_kiri
CROSS JOIN tabel_kanan;
SELECT
k.nama,
d.nama_dept
FROM karyawan k
CROSS JOIN departemen d;
Kalau karyawan ada 5 baris dan departemen ada 4 baris, hasilnya 5 x 4 = 20 baris.
Kapan Pakai CROSS JOIN?
- Bikin kombinasi untuk analisis (misal: semua kombinasi produk dan region)
- Generate test data
- Bikin date dimension table
Hati-hati: CROSS JOIN bisa bikin hasil yang BESAR BANGET. Kalau dua tabel masing-masing 10.000 baris, hasilnya 100 juta baris!
| Jenis JOIN | Hasil | Use Case |
|---|---|---|
| INNER JOIN | Hanya data yang match di kedua tabel | Report standar, data yang valid aja |
| LEFT JOIN | Semua dari kiri + match dari kanan | Keep semua data utama, cek missing relations |
| RIGHT JOIN | Semua dari kanan + match dari kiri | Jarang dipakai, bisa diganti LEFT JOIN |
| FULL OUTER JOIN | Semua dari kedua tabel | Data reconciliation, audit |
| CROSS JOIN | Semua kombinasi | Generate combinations, test data |
-- SALAH: Ini jadi CROSS JOIN!
SELECT * FROM karyawan k JOIN departemen d;
-- BENAR
SELECT * FROM karyawan k JOIN departemen d ON k.departemen_id = d.id;
-- SALAH: Kolom 'id' ada di kedua tabel
SELECT id, nama FROM karyawan k JOIN departemen d ON k.departemen_id = d.id;
-- BENAR: Pakai alias
SELECT k.id, k.nama FROM karyawan k JOIN departemen d ON k.departemen_id = d.id;
Ini yang paling sering terjadi. Pastiin kamu paham data mana yang mau di-keep:
- Mau keep semua karyawan? Pakai LEFT JOIN
- Mau keep semua departemen? Pakai RIGHT JOIN (atau swap dan pakai LEFT)
- Cuma mau yang match? Pakai INNER JOIN
Kalau query JOIN kamu lambat, kemungkinan besar kolom yang dipakai buat JOIN ga ada index-nya. Pastikan kolom foreign key udah di-index.
CREATE INDEX idx_karyawan_dept ON karyawan(departemen_id);
Coba kerjain soal ini pakai dataset karyawan dan departemen di atas:
Soal 1: Tampilkan nama karyawan dan nama departemen untuk semua karyawan di lokasi 'Sudirman'
SELECT k.nama, d.nama_dept
FROM karyawan k
INNER JOIN departemen d ON k.departemen_id = d.id
WHERE d.lokasi = 'Sudirman';
Soal 2: Hitung jumlah karyawan per departemen, termasuk departemen yang kosong
SELECT
d.nama_dept,
COUNT(k.id) AS jumlah_karyawan
FROM departemen d
LEFT JOIN karyawan k ON d.id = k.departemen_id
GROUP BY d.nama_dept;
Soal 3: Cari karyawan yang belum di-assign ke departemen manapun
SELECT k.nama, k.gaji
FROM karyawan k
LEFT JOIN departemen d ON k.departemen_id = d.id
WHERE d.id IS NULL;
INNER JOIN ambil data yang match di kedua tabel. Paling sering dipakai untuk report standar.
LEFT JOIN keep semua data dari tabel kiri. Berguna buat cek data yang ga punya relasi.
RIGHT JOIN kebalikan LEFT JOIN, tapi jarang dipakai. Lebih baik swap tabel dan pakai LEFT JOIN.
FULL OUTER JOIN ambil semua data dari kedua tabel. Cocok buat data reconciliation.
Selalu pakai alias buat menghindari ambiguous column error dan bikin query lebih readable.
Pakai INNER JOIN kalau kamu cuma butuh data yang match di kedua tabel, tanpa peduli sama baris yang nggak punya pasangan. Contohnya laporan penjualan yang cuma nampilin order yang udah ada customer-nya. Kalau kamu masih mau lihat data yang nggak punya relasi, kayak karyawan yang belum di-assign ke departemen, baru pakai LEFT JOIN.
NULL muncul soalnya baris dari tabel kiri nggak punya pasangan di tabel kanan. Di contoh karyawan dan departemen, Rudi Hartono punya departemen_id NULL, jadi kolom nama_dept dan lokasi ikut NULL waktu kamu LEFT JOIN. Itu nandain data itu emang belum di-assign ke departemen manapun. Kamu bisa rapiin tampilannya pakai COALESCE biar nggak keliatan NULL.
LEFT JOIN ambil semua data dari tabel kiri plus yang match dari tabel kanan. RIGHT JOIN kebalikannya, ambil semua dari tabel kanan plus yang match dari kiri. Tapi di praktek RIGHT JOIN jarang dipakai, soalnya kamu bisa dapetin hasil yang sama dengan tukar posisi tabel terus pakai LEFT JOIN, yang jauh lebih gampang dibaca.
MySQL emang nggak support FULL OUTER JOIN secara native. Akalinnya, kamu gabungin LEFT JOIN dan RIGHT JOIN pakai UNION, jadi hasilnya sama kayak FULL OUTER JOIN, semua data dari kedua tabel muncul, yang match ke-match, sisanya NULL. Trik ini cocok buat data reconciliation atau audit dua sistem yang harusnya sama.
Biasanya gara-gara kolom yang dipakai buat JOIN belum di-index. Kalau kolom foreign key kayak departemen_id nggak punya index, database harus scan seluruh tabel buat cari data yang match, apalagi kalau tabelnya udah jutaan baris. Solusinya bikin index di kolom itu pakai CREATE INDEX, biar proses pencarian jauh lebih cepat.
Kalau kamu udah paham JOIN, next step-nya:
- Cara Menggunakan Subquery di SQL
- Common Table Expression (CTE) di SQL
- Window Functions SQL
Teori doang ga cukup. Langsung praktek di NgulikSQL buat latihan soal JOIN dengan dataset Indonesia. Atau kalau mau explore lebih, coba SQLBolt dan DataLemur.
Ada pertanyaan? Comment di bawah atau DM aku di Instagram @ngulikdata.
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.