LEFT JOIN Menyimpan Semua yang Ada di Kiri
INNER JOIN hanya mengembalikan baris yang cocok di kedua sisi. Sering kali itulah yang kamu mau, tapi tidak selalu. Terkadang "tidak ada pasangan" itu sendiri adalah jawaban yang kamu cari: pengguna yang belum pernah memesan, produk yang tidak pernah terjual, postingan tanpa komentar. Untuk kasus seperti itu, kamu butuh LEFT JOIN.
LEFT JOIN mengembalikan setiap baris dari tabel kiri. Jika tabel kanan punya baris yang cocok, kamu mendapat kolom-kolom pasangannya. Jika tidak, baris kirinya tetap kamu dapatkan, dan kolom-kolom sisi kanan bernilai NULL.
Cleo tidak punya pesanan, tapi dia tetap muncul, dengan NULL di kolom total. Ganti LEFT JOIN dengan INNER JOIN, dan Cleo hilang sepenuhnya.
Model Berpikirnya
Baca query-nya dari atas ke bawah dan anggap tabel kiri sebagai jangkarnya. Setiap baris di users pasti muncul di output, apa pun yang terjadi. Lalu, untuk setiap pengguna, LEFT JOIN bertanya: "apakah ada baris yang cocok di orders?"
- Ada pasangan → tempelkan kolom-kolom pasangannya ke baris pengguna.
- Ada beberapa pasangan → hasilkan satu baris output untuk setiap pasangan (Ada punya dua pesanan, jadi dia muncul dua kali).
- Tidak ada pasangan → hasilkan satu baris dengan
NULLuntuk setiap kolom dari tabel kanan.
Kasus terakhir itulah alasan LEFT JOIN ada. NULL di sini tidak berarti "kita tidak tahu", tapi "tidak ada apa pun di kanan yang bisa ditempelkan."
LEFT OUTER JOIN adalah operasi yang sama. Kata kunci OUTER opsional di SQLite, dan kebanyakan orang tidak menuliskannya.
Mencari Baris yang Tidak Punya Pasangan
Kegunaan klasik LEFT JOIN: mencari baris di tabel kiri yang tidak punya pasangan di kanan. Caranya adalah memfilter kolom dari tabel kanan yang bernilai NOT NULL di data aslinya, biasanya primary key-nya, lalu mengecek NULL setelah join:
Hanya Cleo yang muncul. Join menempelkan data pesanan jika ada; lalu WHERE o.id IS NULL hanya menyimpan baris yang gagal ditempeli. Teknik ini kadang disebut "anti-join."
ON vs WHERE: Jebakan yang Halus
Ini bug yang paling sering terjadi pada LEFT JOIN, dan layak dibahas pelan-pelan. Kondisi bisa ditaruh di klausa ON atau klausa WHERE, tapi perilakunya sangat berbeda pada outer join.
ONberjalan selama join berlangsung. Kondisi di sana menentukan baris sisi kanan mana yang dihitung sebagai pasangan.WHEREberjalan setelah join menghasilkan baris-barisnya. Klausa ini memfilter hasil gabungannya.
Perhatikan apa yang terjadi jika kamu menaruh kondisi untuk tabel kanan di WHERE:
Cleo tidak punya pesanan, jadi o.status bernilai NULL untuk barisnya, dan NULL = 'shipped' tidak bernilai benar, sehingga dia tersaring keluar. Status Boris adalah 'pending', jadi ikut dibuang. LEFT JOIN itu diam-diam berperilaku seperti INNER JOIN.
Solusinya: pindahkan kondisi itu ke ON, sehingga yang difilter adalah pasangan, bukan baris output:
Sekarang setiap pengguna muncul. Ada mendapat pesanannya yang sudah dikirim; Boris mendapat NULL (pesanannya yang masih pending tidak memenuhi syarat sebagai pasangan); Cleo mendapat NULL (tidak punya pesanan sama sekali). Itulah jawaban yang benar jika pertanyaannya "tampilkan setiap pengguna, beserta pesanan mereka yang sudah dikirim jika ada."
Patokannya: kondisi untuk tabel kiri boleh ditaruh di WHERE. Kondisi untuk tabel kanan hampir selalu seharusnya ada di ON, kecuali jika kamu memang ingin mencari baris yang tidak punya pasangan dengan IS NULL.
Menghitung dengan LEFT JOIN
Tugas yang umum: menghitung baris terkait untuk setiap induk, termasuk induk yang jumlahnya nol. INNER JOIN akan membuang yang nol. LEFT JOIN ditambah COUNT pada kolom sisi kanan memberi jawaban yang benar:
Dua hal yang perlu diperhatikan:
COUNT(o.id)menghitung baris sisi kanan yang tidak null. Cleo mendapat0, bukan1, karenaCOUNTmengabaikanNULL. Jika kamu menulisCOUNT(*), Cleo akan mendapat1(barisnya ada, hanya saja berisi NULL). Hampir selalu,COUNT(right.id)adalah yang kamu mau.COALESCE(SUM(o.total), 0)mengubah jumlahNULLmilik Cleo menjadi0. Tanpa itu, pendapatannya akan tampilNULL, yang secara teknis benar tapi jelek untuk ditampilkan.
Join Banyak Tabel
LEFT JOIN bisa dirangkai. Setiap join mengambil hasil sementara dan menggabungkan tabel lain ke dalamnya. Begitu sebuah kolom bisa bernilai null karena LEFT JOIN, tetap pakai LEFT JOIN untuk tabel-tabel yang bergantung padanya. Kalau tidak, INNER JOIN berikutnya akan diam-diam membuang baris yang ingin kamu simpan.
Tiga pengguna muncul. Ada punya pesanan dan pengiriman. Boris punya pesanan tapi belum ada pengirimannya (carrier bernilai NULL). Cleo tidak punya pesanan, jadi o.total dan s.carrier sama-sama NULL. Rangkaian LEFT JOIN itu mempertahankan setiap pengguna, sejauh apa pun datanya habis di sepanjang rantai relasi.
Kapan LEFT JOIN Adalah Pilihan yang Tepat
Pakai LEFT JOIN jika pertanyaannya pada dasarnya tentang tabel kiri, dan tabel kanan hanyalah informasi tambahan. Kalimat seperti "setiap pengguna, beserta pesanannya jika ada" atau "semua produk dan ulasan terbarunya" langsung sejalan dengan LEFT JOIN.
Pakai INNER JOIN jika kedua sisi sama-sama wajib ada. "Pesanan beserta detail penggunanya" tidak masuk akal untuk pesanan yang tidak punya pengguna, jadi penyaringan inner join itulah yang kamu mau.
Jika kamu mendapati dirimu menulis LEFT JOIN ... WHERE right.col IS NOT NULL, sebenarnya kamu butuh INNER JOIN. Jika kamu mendapati dirimu menulis LEFT JOIN ... WHERE right.col IS NULL, berarti kamu butuh anti-join, dan caramu sudah benar.
Selanjutnya: Self-Join
Terkadang tabel yang ingin kamu join ke adalah tabel yang sama dengan yang sedang kamu query: karyawan dan manajernya, kategori dan induknya, pasangan pengguna di kota yang sama. Itulah self-join, dan topik itu ada di halaman berikutnya.
Pertanyaan yang sering diajukan
Apa fungsi LEFT JOIN di SQLite?
LEFT JOIN mengembalikan setiap baris dari tabel kiri, ditambah baris yang cocok dari tabel kanan jika ada. Jika tabel kanan tidak punya pasangan, baris kiri tetap kamu dapatkan, dan kolom-kolom sisi kanan bernilai NULL. LEFT OUTER JOIN sama saja: kata OUTER opsional di SQLite.
Apa perbedaan LEFT JOIN dan INNER JOIN di SQLite?
INNER JOIN hanya mengembalikan baris yang kondisi join-nya cocok di kedua tabel. LEFT JOIN mengembalikan semua baris dari tabel kiri apa pun yang terjadi, dan mengisi kolom sisi kanan yang tidak punya pasangan dengan NULL. Pakai LEFT JOIN jika 'tidak ada pasangan' itu sendiri adalah jawaban yang bermakna, misalnya pengguna yang belum punya pesanan sama sekali.
Kenapa LEFT JOIN saya di SQLite berperilaku seperti INNER JOIN?
Hampir selalu karena klausa WHERE yang memfilter kolom sisi kanan tanpa memperhitungkan NULL. Kondisi untuk tabel kanan seharusnya ada di klausa ON, bukan WHERE, atau kamu perlu menulis WHERE right.col IS NULL untuk mencari baris yang tidak punya pasangan. WHERE right.col = 'x' diam-diam membuang setiap baris yang tidak punya pasangan.