TL;DR
LEFT JOIN nggabungin dua tabel tapi tetap nyimpen semua baris dari tabel kiri, walau gak ada pasangannya di tabel kanan. Baris yang gak ketemu pasangan bakal diisi NULL di kolom-kolom tabel kanan. Ini JOIN yang kamu pakai buat nemuin yang gak punya pasangan, misalnya pelanggan yang belum pernah transaksi atau produk yang belum pernah laku.
LEFT JOIN nggabungin dua tabel dan nyimpen semua baris dari tabel kiri, pasangan ada atau nggak. Baris yang nggak ketemu pasangan di tabel kanan bakal diisi NULL.
Bedanya sama INNER JOIN cuma di satu hal: INNER JOIN buang baris yang jomblo, LEFT JOIN nggak.
Justru di situ gunanya. Pertanyaan bisnis paling mahal seringnya soal ketidakhadiran. Pelanggan mana yang belum pernah balik belanja? Produk mana yang nganggur di rak? LEFT JOIN yang jawab.
LEFT JOIN adalah operasi yang nyocokin baris dari dua tabel berdasarkan kolom kunci, tapi tetap nampilin semua baris tabel kiri walau nggak ada yang cocok di tabel kanan. Kolom dari tabel kanan yang nggak punya pasangan diisi NULL.
SELECT kolom
FROM tabel_kiri
LEFT JOIN tabel_kanan
ON tabel_kiri.kunci = tabel_kanan.kunci;
Kata OUTER boleh ditambahin (LEFT OUTER JOIN), artinya sama persis. Kebanyakan orang nulis LEFT JOIN aja.
Yang disebut "kiri" itu tabel yang ditulis di FROM. Tabel yang ditulis setelah LEFT JOIN itu kanan. Urutan ini nentuin hasil, jadi jangan asal tukar.
Aku pakai data Toko Berkah. Tabel pelanggan isinya 5 toko langganan, tabel transaksi isinya belanja mereka.
CREATE TABLE pelanggan (
id_pelanggan INTEGER PRIMARY KEY,
nama TEXT,
kota TEXT,
tgl_daftar TEXT
);
CREATE TABLE transaksi (
id_transaksi INTEGER PRIMARY KEY,
id_pelanggan INTEGER,
tanggal TEXT,
nominal INTEGER
);
INSERT INTO pelanggan VALUES
(1,'Warung Maju','Bandung','2026-01-05'),
(2,'Kios Sejahtera','Surabaya','2026-01-11'),
(3,'Toko Amanah','Bandung','2026-01-18'),
(4,'Warung Bu Ani','Medan','2026-02-02'),
(5,'Kedai Pak Slamet','Surabaya','2026-02-09');
INSERT INTO transaksi VALUES
(101, 1, '2026-02-10', 1250000),
(102, 1, '2026-02-14', 875000),
(103, 2, '2026-02-11', 2140000),
(104, 3, '2026-02-13', 640000);
Perhatiin: pelanggan 4 dan 5 nggak punya transaksi sama sekali. Mereka daftar, tapi belum pernah belanja.
SELECT p.nama, p.kota, t.tanggal, t.nominal
FROM pelanggan p
LEFT JOIN transaksi t ON p.id_pelanggan = t.id_pelanggan
ORDER BY p.id_pelanggan;
| nama | kota | tanggal | nominal |
|---|---|---|---|
| Warung Maju | Bandung | 2026-02-10 | 1.250.000 |
| Warung Maju | Bandung | 2026-02-14 | 875.000 |
| Kios Sejahtera | Surabaya | 2026-02-11 | 2.140.000 |
| Toko Amanah | Bandung | 2026-02-13 | 640.000 |
| Warung Bu Ani | Medan | NULL | NULL |
| Kedai Pak Slamet | Surabaya | NULL | NULL |
Warung Bu Ani dan Kedai Pak Slamet tetap muncul, kolom transaksinya NULL. Kalau ini INNER JOIN, dua baris itu bakal hilang.
Warung Maju muncul dua kali, soalnya dia punya dua transaksi. Ini normal, bukan bug.
Ini pola paling berguna dari LEFT JOIN, namanya anti-join. Caranya: LEFT JOIN dulu, terus saring baris yang kolom kanannya NULL.
SELECT p.nama, p.kota, p.tgl_daftar
FROM pelanggan p
LEFT JOIN transaksi t ON p.id_pelanggan = t.id_pelanggan
WHERE t.id_transaksi IS NULL;
| nama | kota | tgl_daftar |
|---|---|---|
| Warung Bu Ani | Medan | 2026-02-02 |
| Kedai Pak Slamet | Surabaya | 2026-02-09 |
Dua toko ini yang perlu ditelpon. Dari 5 pelanggan terdaftar, 2 belum pernah transaksi. 40% pendaftar nggak pernah konversi jadi pembeli.
Kalau di sistem beneran angkanya mirip, itu masalah onboarding, bukan masalah produk. Dan kamu nggak bakal ketemu angka ini pakai INNER JOIN, soalnya orang-orang ini justru dibuang duluan.
Kuncinya di IS NULL. Kolom NULL nggak bisa dibandingin pakai =. Kalau kamu nulis WHERE t.id_transaksi = NULL, hasilnya kosong melompong. Aku bahas kenapa di NULL di SQL.
Ini kesalahan paling sering, dan paling susah ketahuan. Query-nya jalan, hasilnya keluar, tapi datanya bohong.
-- Niatnya: semua pelanggan, plus transaksi di atas 500rb
SELECT p.nama, t.nominal
FROM pelanggan p
LEFT JOIN transaksi t ON p.id_pelanggan = t.id_pelanggan
WHERE t.nominal > 500000;
Warung Bu Ani dan Kedai Pak Slamet hilang dari hasil. Padahal kamu pakai LEFT JOIN.
Penyebabnya: nominal mereka NULL. Perbandingan NULL > 500000 hasilnya bukan TRUE, bukan FALSE, tapi "nggak tau". Baris yang nggak lolos WHERE langsung dibuang. LEFT JOIN kamu berubah jadi INNER JOIN tanpa permisi.
Perbaikannya: pindahin syarat filter tabel kanan ke klausa ON.
SELECT p.nama, t.nominal
FROM pelanggan p
LEFT JOIN transaksi t
ON p.id_pelanggan = t.id_pelanggan
AND t.nominal > 500000;
Sekarang semua 5 pelanggan muncul. Yang nggak punya transaksi di atas 500rb nominalnya NULL, tapi barisnya tetap ada.
Aturan yang gampang diinget: syarat buat tabel kanan taruh di ON, syarat buat tabel kiri taruh di WHERE.
Rekap yang paling sering diminta bos. Semua pelanggan harus muncul, yang belum belanja nilainya 0.
SELECT p.nama,
COUNT(t.id_transaksi) AS jumlah_transaksi,
COALESCE(SUM(t.nominal), 0) AS total_belanja
FROM pelanggan p
LEFT JOIN transaksi t ON p.id_pelanggan = t.id_pelanggan
GROUP BY p.nama
ORDER BY total_belanja DESC;
| nama | jumlah_transaksi | total_belanja |
|---|---|---|
| Kios Sejahtera | 1 | 2.140.000 |
| Warung Maju | 2 | 2.125.000 |
| Toko Amanah | 1 | 640.000 |
| Warung Bu Ani | 0 | 0 |
| Kedai Pak Slamet | 0 | 0 |
Dua detail penting di query ini.
Pertama, COALESCE(SUM(...), 0). Tanpa itu, total belanja Warung Bu Ani keluar NULL, bukan 0. SUM dari kumpulan NULL hasilnya NULL. Di dashboard, NULL bikin chart-nya bolong. Aku bahas COALESCE lebih dalam di COALESCE SQL.
Kedua, aku pakai COUNT(t.id_transaksi), bukan COUNT(*). COUNT(*) bakal ngitung baris NULL sebagai 1, jadi Warung Bu Ani keliatan punya 1 transaksi. COUNT di kolom tertentu ngelewatin NULL, hasilnya 0. Bener.
COUNT(kolom_dari_tabel_kanan).pelanggan LEFT JOIN transaksi beda jauh dari transaksi LEFT JOIN pelanggan. Yang di FROM itu yang dipertahanin.= NULL buat nyari yang kosong. Nggak akan pernah jalan. Pakai IS NULL.LEFT JOIN nggabungin dua tabel dan nyimpen semua baris dari tabel kiri, walau nggak ada pasangannya di tabel kanan. Baris yang nggak ketemu pasangan diisi NULL di kolom tabel kanan. Contoh paling umum: gabungin tabel pelanggan ke tabel transaksi, biar pelanggan yang belum pernah belanja tetap muncul di hasil.
INNER JOIN cuma nyisain baris yang punya pasangan di dua tabel, jadi baris jomblo dibuang. LEFT JOIN nyimpen semua baris tabel kiri, pasangan ada atau nggak. Pakai INNER JOIN kalau kamu cuma butuh data lengkap. Pakai LEFT JOIN kalau justru yang nggak punya pasangan itu yang menarik buat kamu.
Hampir pasti gara-gara ada kondisi WHERE yang nyentuh kolom tabel kanan. Baris tanpa pasangan punya nilai NULL di kolom kanan, dan NULL nggak pernah lolos perbandingan biasa kayak > atau =. Efeknya baris itu kebuang, dan hasilnya persis kayak INNER JOIN. Pindahin syarat filter tabel kanan ke klausa ON.
Pakai LEFT JOIN, terus saring pakai WHERE kolom_kanan IS NULL. Contohnya WHERE t.id_transaksi IS NULL bakal nyisain cuma pelanggan yang nggak punya satu pun transaksi. Pola ini namanya anti-join, dan ini cara paling umum buat nemuin pelanggan pasif atau produk yang belum pernah laku.
Soalnya baris tanpa pasangan isinya NULL, dan SUM dari kumpulan NULL hasilnya juga NULL, bukan nol. Bungkus pakai COALESCE biar keluar angka: COALESCE(SUM(t.nominal), 0). Ini penting kalau angkanya mau dipakai buat hitungan lanjutan atau ditampilin di dashboard.
LEFT JOIN kepake tiap kali ketidakhadiran itu informasi. Pelanggan yang belum belanja, produk yang belum laku, karyawan yang belum ngumpulin laporan.
Dua hal yang wajib nempel di kepala. Filter kolom tabel kanan taruh di ON, bukan WHERE. Dan bungkus SUM pakai COALESCE biar angkanya 0, bukan NULL.
Sintaks lengkap LEFT JOIN beda-beda tipis per database, dan konsep NULL yang jadi biang keroknya ada di glossary NULL. Referensi resminya bisa kamu baca di dokumentasi 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.