TL;DR
INNER JOIN nggabungin dua tabel lewat kolom kunci yang sama, dan cuma nyisain baris yang ketemu pasangannya di kedua tabel. Baris yang gak punya pasangan langsung dibuang dari hasil, jadi jumlah baris hasil query bisa lebih sedikit dari tabel aslinya. Ini JOIN paling sering dipakai analis, biasanya buat nyambungin tabel transaksi ke tabel master produk atau pelanggan.
INNER JOIN nggabungin dua tabel lewat kolom kunci yang nilainya sama, dan cuma nyisain baris yang ketemu pasangannya di dua-duanya. Baris yang jomblo langsung dibuang dari hasil.
Ini JOIN yang paling sering kamu pakai sebagai analis. Tabel transaksi biasanya cuma nyimpen id_produk, bukan nama produknya. Nama produknya ada di tabel lain. INNER JOIN yang nyambungin dua-duanya.
Kalau kamu baru pertama kali ketemu JOIN, tenang. Query pertamanya cuma 4 baris.
INNER JOIN adalah operasi yang nyocokin baris dari dua tabel berdasarkan kolom kunci, terus nampilin baris yang cocok aja. Kalau satu baris di tabel kiri nggak nemu pasangan di tabel kanan, baris itu nggak muncul di hasil. Begitu juga sebaliknya.
Sintaks dasarnya:
SELECT kolom_yang_kamu_mau
FROM tabel_kiri
INNER JOIN tabel_kanan
ON tabel_kiri.kunci = tabel_kanan.kunci;
Kata INNER boleh dihapus. JOIN doang artinya sama persis dengan INNER JOIN di semua database populer. Tapi aku saranin tetap ditulis biar niat kamu jelas kebaca orang lain.
Database ngecek tiap baris di tabel kiri, terus nyari baris di tabel kanan yang nilai kuncinya sama. Ketemu, dua baris itu digabung jadi satu baris panjang. Nggak ketemu, barisnya dilewatin.
Aku pakai data Toko Berkah buat contohnya. Dua tabel: produk (master) dan penjualan (transaksi).
CREATE TABLE produk (
id_produk INTEGER PRIMARY KEY,
nama_produk TEXT,
kategori TEXT,
harga INTEGER
);
CREATE TABLE penjualan (
id_penjualan INTEGER PRIMARY KEY,
tanggal TEXT,
id_produk INTEGER,
qty INTEGER
);
INSERT INTO produk VALUES
(1,'Beras Premium 5kg','Sembako', 68500),
(2,'Minyak Goreng 2L','Sembako', 34750),
(3,'Gula Pasir 1kg','Sembako', 16250),
(4,'Kopi Bubuk 200g','Minuman', 21375),
(5,'Teh Celup 25s','Minuman', 12000);
INSERT INTO penjualan VALUES
(101,'2026-02-10', 1, 3),
(102,'2026-02-10', 3, 12),
(103,'2026-02-11', 2, 7),
(104,'2026-02-11', 4, 9),
(105,'2026-02-12', 1, 2),
(106,'2026-02-12', 9, 4);
Perhatiin baris terakhir. Transaksi 106 nyatet id_produk = 9, padahal produk nomor 9 nggak ada di tabel produk. Data kotor kayak gini normal di sistem beneran, biasanya sisa produk yang udah dihapus.
Sekarang JOIN-nya:
SELECT p.tanggal, pr.nama_produk, p.qty, pr.harga
FROM penjualan p
INNER JOIN produk pr ON p.id_produk = pr.id_produk
ORDER BY p.tanggal;
Hasilnya:
| tanggal | nama_produk | qty | harga |
|---|---|---|---|
| 2026-02-10 | Beras Premium 5kg | 3 | 68.500 |
| 2026-02-10 | Gula Pasir 1kg | 12 | 16.250 |
| 2026-02-11 | Minyak Goreng 2L | 7 | 34.750 |
| 2026-02-11 | Kopi Bubuk 200g | 9 | 21.375 |
| 2026-02-12 | Beras Premium 5kg | 2 | 68.500 |
Lima baris. Padahal tabel penjualan isinya enam. Transaksi 106 raib, soalnya id_produk = 9 nggak nemu pasangan.
Ini sifat INNER JOIN yang wajib kamu inget: dia diam-diam ngebuang baris. Nggak ada warning, nggak ada error. Kalau kamu nggak ngecek, omzet transaksi 106 hilang dari laporan.
Makanya biasain cek jumlah baris sebelum dan sesudah JOIN:
SELECT COUNT(*) FROM penjualan; -- 6
SELECT COUNT(*) FROM penjualan p
INNER JOIN produk pr ON p.id_produk = pr.id_produk; -- 5
Beda satu. Itu sinyal buat kamu nyelidikin, bukan langsung lanjut bikin chart.
JOIN sendirian jarang jadi tujuan akhir. Biasanya dia langkah pertama sebelum diagregasi. Ini query yang paling sering aku tulis buat rekap toko:
SELECT pr.nama_produk,
SUM(p.qty) AS total_qty,
SUM(p.qty * pr.harga) AS total_omzet
FROM penjualan p
INNER JOIN produk pr ON p.id_produk = pr.id_produk
GROUP BY pr.nama_produk
ORDER BY total_omzet DESC;
Hasilnya:
| nama_produk | total_qty | total_omzet |
|---|---|---|
| Beras Premium 5kg | 5 | 342.500 |
| Minyak Goreng 2L | 7 | 243.250 |
| Gula Pasir 1kg | 12 | 195.000 |
| Kopi Bubuk 200g | 9 | 192.375 |
Ada temuan menarik di sini. Gula kejual 12 unit, jauh lebih laris dari beras yang cuma 5 unit. Tapi omzet beras hampir dua kali lipat omzet gula.
Kalau pemilik toko cuma lihat kolom qty, dia bakal nambahin stok gula dan ngurangin beras. Padahal yang nyumbang duit paling besar justru beras. Ini alasan kenapa JOIN penting: angka qty doang nggak cukup buat ambil keputusan.
Teh Celup nggak muncul sama sekali di hasil, soalnya nggak pernah kejual. INNER JOIN ngebuang produk yang nggak punya transaksi. Kalau kamu justru mau lihat produk yang belum laku, pakai LEFT JOIN.
ON nentuin gimana dua tabel disambungin. WHERE nyaring hasilnya setelah nyambung. Di INNER JOIN, dua-duanya ngasih hasil sama, tapi bacanya beda.
-- Syarat penyambung di ON, filter di WHERE (rekomendasi)
SELECT pr.nama_produk, SUM(p.qty * pr.harga) AS omzet
FROM penjualan p
INNER JOIN produk pr ON p.id_produk = pr.id_produk
WHERE pr.kategori = 'Sembako'
GROUP BY pr.nama_produk;
Hasilnya cuma tiga produk sembako, total omzet Rp 780.750.
Kenapa kebiasaan ini penting? Soalnya di LEFT JOIN, naruh filter di ON atau WHERE bikin hasil yang beda jauh. Kalau kamu udah biasain dari INNER JOIN, kamu nggak bakal kejebak nanti.
id_produk muncul dua kali di tabel produk, tiap transaksi yang cocok bakal digandain. Omzet kamu tiba-tiba dua kali lipat. Cek keunikan kunci pakai SELECT id_produk, COUNT(*) FROM produk GROUP BY id_produk HAVING COUNT(*) > 1;p dan pr, terus tulis p.qty, bukan qty polos.id_produk bertipe TEXT di satu tabel dan INTEGER di tabel lain? Hasilnya kosong tanpa error. Samain tipenya dulu.INNER JOIN adalah cara nggabungin dua tabel lewat kolom kunci yang nilainya sama. Hasilnya cuma baris yang ketemu pasangannya di kedua tabel. Baris yang nggak punya pasangan langsung dibuang. Contoh paling umum: nyambungin tabel penjualan ke tabel produk lewat id_produk, biar kamu bisa lihat nama produk, bukan cuma angka id-nya.
INNER JOIN cuma nyisain baris yang punya pasangan di dua tabel. LEFT JOIN nyimpen semua baris dari tabel kiri, walau nggak ada pasangannya di tabel kanan, dan kolom kanannya diisi NULL. Pakai INNER JOIN kalau kamu cuma butuh data yang lengkap. Pakai LEFT JOIN kalau kamu justru mau nemuin yang nggak punya pasangan, misalnya produk yang belum pernah laku.
Soalnya kolom kunci di salah satu tabel nggak unik. Kalau satu id_produk muncul dua kali di tabel produk, tiap baris penjualan yang cocok bakal digandain jadi dua. Cek dulu keunikan kuncinya pakai COUNT dan GROUP BY sebelum JOIN. Kalau totalnya membengkak, hampir pasti masalahnya di sini, bukan di query JOIN-nya.
ON nentuin gimana dua tabel disambungin, WHERE nyaring hasil setelah disambungin. Di INNER JOIN, naruh syarat di ON atau WHERE hasilnya sama. Tapi di LEFT JOIN hasilnya beda jauh, jadi biasain dari sekarang: syarat penyambung taruh di ON, syarat filter taruh di WHERE.
Bisa. Tinggal tumpuk INNER JOIN berikutnya di bawahnya, misalnya penjualan JOIN produk lalu JOIN kategori. Tiap JOIN butuh klausa ON sendiri. Tapi hati-hati, tiap tabel tambahan yang kuncinya nggak unik bisa gandain baris. Cek jumlah barisnya tiap kali kamu nambah satu JOIN baru.
INNER JOIN itu pintu masuk ke hampir semua analisis serius. Tabel transaksi nyimpen id, tabel master nyimpen artinya, dan JOIN yang nyatuin.
Dua kebiasaan yang bakal nyelametin kamu: cek COUNT sebelum dan sesudah JOIN, dan pastiin kolom kuncinya unik di tabel master.
Sintaks lengkap INNER JOIN beda-beda tipis per database, dan istilah kunci penyambungnya aku jelasin di glossary foreign key. Referensi resminya bisa kamu cek di dokumentasi JOIN PostgreSQL.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
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.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.