Klausa ON sejauh ini hanya kita isi satu persamaan kunci. Padahal ON adalah kondisi boolean penuh: boleh memuat AND, OR, pembandingan apa pun, bahkan kondisi yang tidak menyentuh kolom kunci sama sekali. Fleksibilitas ini membuka dua kemampuan penting. Pertama, kondisi tambahan di ON — misalnya “hanya pesanan setelah tanggal tertentu” — bisa bergabung langsung ke proses pencocokan join. Kedua, dan inilah fokus utama chapter ini, letak penulisan kondisi berakibat besar pada LEFT JOIN: kondisi di ON menjaga baris kiri tetap tampil, sedangkan kondisi yang sama dipindah ke WHERE bisa diam-diam mengubah LEFT JOIN menjadi mirip INNER JOIN. Kita membuktikan keduanya dengan eksperimen berdampingan, lalu menutup dengan non-equi join — join yang kondisinya pertidaksamaan, bukan kesamaan.

Menyiapkan Pasangan Tabel Latihan #

Data tetap pasangan pelanggan dan pesanan. Untuk mempertajam percobaan nanti, sebaran tanggal sengaja dibuat bervariasi: Budi memesan sebelum dan sesudah tanggal 2026-01-11, Siti juga, Andi hanya sesudah, sementara Rina hanya memesan sebelum tanggal itu — dan tiga pelanggan tidak memesan sama sekali:

sql
DROP DATABASE IF EXISTS belajar_join;
CREATE DATABASE belajar_join;
USE belajar_join;

CREATE TABLE pelanggan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(50) NOT NULL,
  kota VARCHAR(30) NOT NULL
);

CREATE TABLE pesanan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  pelanggan_id INT NOT NULL,
  tanggal DATE NOT NULL,
  total DECIMAL(12,2) NOT NULL,
  FOREIGN KEY (pelanggan_id) REFERENCES pelanggan(id)
);

INSERT INTO pelanggan (nama, kota) VALUES
  ('Budi Santoso', 'Jakarta'),
  ('Siti Rahayu',  'Bandung'),
  ('Andi Wijaya',  'Surabaya'),
  ('Rina Melati',  'Yogyakarta'),
  ('Dewi Lestari', 'Bandung'),
  ('Tono Hartono', 'Medan'),
  ('Sari Puspita', 'Jakarta');

INSERT INTO pesanan (pelanggan_id, tanggal, total) VALUES
  (1, '2026-01-05', 170000.00),
  (2, '2026-01-07',  50000.00),
  (1, '2026-01-10',  62000.00),
  (1, '2026-01-12',  15000.00),
  (3, '2026-01-15', 137000.00),
  (2, '2026-01-18',  85000.00),
  (4, '2026-01-08', 125000.00);

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

Ambang tanggal 2026-01-11 akan kita pakai sebagai kondisi tambahan. Pesanan yang lolos ambang itu hanya tiga: milik Budi (2026-01-12), Andi (2026-01-15), dan Siti (2026-01-18). Rina punya pesanan tetapi semuanya sebelum ambang — kelompok “punya pesanan, tapi tidak lolos syarat” inilah yang akan memperlihatkan perbedaan ON dan WHERE secara paling dramatis.

Kondisi Tambahan di Klausa ON #

Klausa ON menerima syarat gabungan lewat operator AND. Query berikut memasangkan pelanggan dengan pesanannya, tetapi hanya pesanan bertanggal 2026-01-11 ke atas yang boleh jadi pasangan:

sql
SELECT p.nama, pn.tanggal, pn.total
FROM pelanggan p
INNER JOIN pesanan pn
  ON p.id = pn.pelanggan_id AND pn.tanggal >= '2026-01-11'
ORDER BY pn.tanggal;

Output yang Dihasilkan:

text
+--------------+------------+-----------+
| nama         | tanggal    | total     |
+--------------+------------+-----------+
| Budi Santoso | 2026-01-12 |  15000.00 |
| Andi Wijaya  | 2026-01-15 | 137000.00 |
| Siti Rahayu  | 2026-01-18 |  85000.00 |
+--------------+------------+-----------+
3 rows in set (0.00 sec)

Kondisi ON p.id = pn.pelanggan_id AND pn.tanggal >= '2026-01-11' dibaca sebagai satu kesatuan: sebuah baris pesanan layak menjadi pasangan bila pemiliknya cocok dan tanggalnya lolos ambang. Hasilnya tiga baris — Budi, Andi, dan Siti — sama persis dengan hasil INNER JOIN biasa yang kemudian disaring WHERE pn.tanggal >= '2026-01-11' di chapter INNER JOIN. Pada INNER JOIN, menaruh syarat di ON atau di WHERE memang tidak mengubah hasil; keduanya menyaring pasangan sebelum baris final terbentuk. Ketidaksetaraan baru muncul ketika jenis join-nya LEFT JOIN — dan itulah eksperimen inti kita berikutnya.

ON versus WHERE pada LEFT JOIN #

Sekarang ganti INNER menjadi LEFT dan pertahankan syarat tanggal di ON. Query berikut bertanya: tampilkan semua pelanggan, bersama pesanan mereka jika pesanan itu setelah 2026-01-11:

sql
SELECT p.nama, pn.tanggal, pn.total
FROM pelanggan p
LEFT JOIN pesanan pn
  ON p.id = pn.pelanggan_id AND pn.tanggal >= '2026-01-11'
ORDER BY p.id;

Output yang Dihasilkan:

text
+--------------+------------+-----------+
| nama         | tanggal    | total     |
+--------------+------------+-----------+
| Budi Santoso | 2026-01-12 |  15000.00 |
| Siti Rahayu  | 2026-01-18 |  85000.00 |
| Andi Wijaya  | 2026-01-15 | 137000.00 |
| Rina Melati  | NULL       |      NULL |
| Dewi Lestari | NULL       |      NULL |
| Tono Hartono | NULL       |      NULL |
| Sari Puspita | NULL       |      NULL |
+--------------+------------+-----------+
7 rows in set (0.00 sec)

Hasilnya tujuh baris — semua pelanggan hadir. Budi tampil dengan pesanan 2026-01-12 miliknya; pesanan Budi yang lebih awal (2026-01-05 dan 2026-01-10) gagal memenuhi syarat ON sehingga tidak pernah menjadi pasangan. Empat pelanggan tampil dengan kolom pesanan NULL: Rina (punya pesanan, tetapi semuanya sebelum ambang), serta Dewi, Tono, dan Sari (tidak punya pesanan sama sekali). Inilah perilaku yang benar: syarat di ON mengatur siapa yang boleh jadi pasangan, sementara jaminan LEFT JOIN — baris kiri selalu tampil — tetap berlaku.

Sekarang pindahkan syarat tanggal yang sama ke WHERE:

sql
SELECT p.nama, pn.tanggal, pn.total
FROM pelanggan p
LEFT JOIN pesanan pn ON p.id = pn.pelanggan_id
WHERE pn.tanggal >= '2026-01-11'
ORDER BY p.id;

Output yang Dihasilkan:

text
+--------------+------------+-----------+
| nama         | tanggal    | total     |
+--------------+------------+-----------+
| Budi Santoso | 2026-01-12 |  15000.00 |
| Siti Rahayu  | 2026-01-18 |  85000.00 |
| Andi Wijaya  | 2026-01-15 | 137000.00 |
+--------------+------------+-----------+
3 rows in set (0.00 sec)

Hasilnya menyusut menjadi tiga baris — Rina, Dewi, Tono, dan Sari hilang. Mari kita bedah mengapa, karena inilah jantung chapter ini:

  1. Tahap join dulu: LEFT JOIN membentuk sepuluh baris gabungan — tujuh baris pesanan berpasangan plus tiga baris NULL untuk pelanggan tanpa pasangan.
  2. Tahap WHERE belakangan: WHERE menyaring hasil join, baris demi baris. Untuk baris NULL, perbandingan pn.tanggal >= '2026-01-11' menghasilkan NULL (bukan benar), sehingga baris tersebut gugur.
  3. Efek akhirnya: setiap baris pelanggan tanpa pesanan-lolos-syarat terbuang. LEFT JOIN yang susah payah mempertahankan baris kiri di tahap join justru diruntuhkan oleh WHERE di tahap berikutnya.

Kita buktikan poin ketiga dengan membandingkan langsung terhadap INNER JOIN dengan WHERE yang sama:

sql
SELECT p.nama, pn.tanggal, pn.total
FROM pelanggan p
INNER JOIN pesanan pn ON p.id = pn.pelanggan_id
WHERE pn.tanggal >= '2026-01-11'
ORDER BY p.id;

Output yang Dihasilkan:

text
+--------------+------------+-----------+
| nama         | tanggal    | total     |
+--------------+------------+-----------+
| Budi Santoso | 2026-01-12 |  15000.00 |
| Siti Rahayu  | 2026-01-18 |  85000.00 |
| Andi Wijaya  | 2026-01-15 | 137000.00 |
+--------------+------------+-----------+
3 rows in set (0.00 sec)

Tiga baris yang identik. LEFT JOIN + WHERE pada kolom kanan terbukti berperilaku sebagai INNER JOIN terselubung. Kesimpulan praktisnya tegas: bila tujuannya “semua baris kiri, dengan pasangan yang memenuhi syarat tertentu”, syaratnya wajib ditulis di ON; bila tujuannya “hanya pasangan yang memenuhi syarat”, gunakan INNER JOIN + WHERE — atau LEFT JOIN + WHERE pn.id IS NOT NULL bila ingin eksplisit. Yang berbahaya adalah wilayah abu-abu: LEFT JOIN + WHERE yang ditulis tanpa disadari padahal niatnya mempertahankan baris kiri.

Non-Equi Join: Kondisi Bukan Kesamaan #

Semua join kita memakai kondisi kesamaan (=). Kondisi pertidaksamaan juga sah di ON — join semacam ini disebut non-equi join, dan kasus nyatanya yang paling klasik adalah pemasangan rentang: tabel promo berisi syarat minimal belanja, dan kita ingin tahu promo mana yang berlaku untuk setiap pesanan. Kita buat tabel kecilnya:

sql
CREATE TABLE promo (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(40) NOT NULL,
  syarat_minimal DECIMAL(12,2) NOT NULL
);

INSERT INTO promo (nama, syarat_minimal) VALUES
  ('Cashback 10 ribu',  50000.00),
  ('Diskon 5 persen',  100000.00),
  ('Gratis ongkir',   200000.00);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Join-nya memasangkan setiap pesanan dengan setiap promo yang syaratnya terpenuhi oleh total pesanan:

sql
SELECT pn.id AS pesanan_id, pn.total, pr.nama AS promo
FROM pesanan pn
INNER JOIN promo pr ON pn.total >= pr.syarat_minimal
ORDER BY pn.id, pr.syarat_minimal;

Output yang Dihasilkan:

text
+------------+-----------+------------------+
| pesanan_id | total     | promo            |
+------------+-----------+------------------+
|          1 | 170000.00 | Cashback 10 ribu |
|          1 | 170000.00 | Diskon 5 persen  |
|          2 |  50000.00 | Cashback 10 ribu |
|          3 |  62000.00 | Cashback 10 ribu |
|          5 | 137000.00 | Cashback 10 ribu |
|          5 | 137000.00 | Diskon 5 persen  |
|          6 |  85000.00 | Cashback 10 ribu |
|          7 | 125000.00 | Cashback 10 ribu |
|          7 | 125000.00 | Diskon 5 persen  |
+------------+-----------+------------------+
9 rows in set (0.00 sec)

Kondisi ON pn.total >= pr.syarat_minimal sama sekali tidak menyamakan kunci; kondisi ini menguji pertidaksamaan antar kolom dua tabel. Hasilnya sembilan baris: pesanan senilai 170000.00 lolos dua promo (Cashback dan Diskon 5 persen) sehingga tampil dua baris, pesanan 15000.00 tidak lolos satu promo pun sehingga tidak tampil, dan seterusnya. Perhatikan dua sifat penting non-equi join dari hasil ini. Pertama, satu baris kiri bisa berpasangan dengan beberapa baris kanan — lawan dari join kunci yang menjamin pasangan tunggal; pertidaksamaan memang tidak membatasi jumlah pasangan. Kedua, ada baris kiri yang tidak mempunyai pasangan sama sekali, sehingga INNER JOIN membuangnya — bila daftar pesanan harus tetap lengkap, LEFT JOIN ... ON pn.total >= pr.syarat_minimal akan menampilkan pesanan 15000.00 dengan kolom promo NULL. Duplikasi baris yang muncul di sini bukan kecelakaan, melainkan sifat bawaan join non-equi — dan mengelolanya justru topik chapter berikutnya.

Membersihkan Database Latihan #

sql
DROP DATABASE belajar_join;

Output yang Dihasilkan:

text
Query OK, 3 rows affected (0.00 sec)

Referensi #

Klausa ON kini tampil dengan kapasitas penuhnya: kondisi majemuk dengan AND, perbedaan nasib syarat di ON versus di WHERE pada LEFT JOIN, hingga pertidaksamaan untuk pemasangan rentang. Satu fenomena muncul berulang di chapter ini — join yang menghasilkan baris lebih banyak dari perkiraan, seperti pesanan yang tampil dua kali karena lolos dua promo. Chapter penutup section ini mengangkat fenomena itu sebagai topik utama: duplikasi hasil join dan strategi menghindarinya.