SQL JOIN menggabungkan baris dari dua tabel dengan mencocokkan kolom yang sama-sama dimiliki keduanya, misalnya author_id milik buku dan id milik penulis. Jenis join menentukan apa yang terjadi pada baris yang tidak cocok. Contoh-contohnya berjalan di PostgreSQL, MySQL, SQLite, dan SQL Server.
Cara menggunakan SQL JOIN
Letakkan tabel kedua setelah JOIN, lalu tentukan bagaimana barisnya cocok dengan baris tabel pertama di ON:
SELECT columns
FROM table_a
JOIN table_b ON table_a.key = table_b.key;
Setiap contoh di bagian ini menggabungkan dua tabel yang sama: Butler menulis dua buku, Asimov tidak menulis satu pun, dan tidak ada yang tahu siapa penulis Beowulf.
INNER JOIN
INNER JOIN hanya mengembalikan baris yang cocok di kedua tabel. JOIN saja berarti INNER JOIN.
-- Input: authors
-- id | name
-- ---+--------
-- 1 | Le Guin
-- 2 | Butler
-- 3 | Asimov
-- Input: books
-- title | author_id
-- ---------------------+----------
-- The Dispossessed | 1
-- Kindred | 2
-- Parable of the Sower | 2
-- Beowulf | NULL
SELECT a.name, b.title
FROM authors a
INNER JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
Asimov tidak punya buku dan Beowulf tidak punya penulis, jadi keduanya tidak muncul.
LEFT JOIN
LEFT JOIN (atau LEFT OUTER JOIN) mengembalikan setiap baris dari tabel kiri, yaitu tabel setelah FROM. Jika tabel kanan tidak punya pasangan, kolomnya bernilai NULL.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- Asimov | NULL
RIGHT JOIN
RIGHT JOIN (atau RIGHT OUTER JOIN) adalah kebalikannya: setiap baris dari tabel kanan, dengan NULL jika tabel kiri tidak punya pasangan.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- NULL | Beowulf
FULL OUTER JOIN
FULL OUTER JOIN mengembalikan setiap baris dari kedua tabel, dipasangkan jika memungkinkan dan diisi NULL jika tidak. MySQL tidak memilikinya: lihat “Sintaks alternatif” di bawah.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
FULL OUTER JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- NULL | Beowulf
-- Asimov | NULL
CROSS JOIN
CROSS JOIN memasangkan setiap baris satu tabel dengan setiap baris tabel lainnya, jadi tidak ada ON. Dua ukuran dan dua warna menghasilkan empat kombinasi; 1.000 baris kali 1.000 baris menghasilkan satu juta, jadi gunakan pada tabel kecil.
-- Input: sizes
-- size
-- ----
-- S
-- M
-- Input: colors
-- color
-- -----
-- Red
-- Blue
SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;
-- Output:
-- size | color
-- -----+------
-- S | Red
-- S | Blue
-- M | Red
-- M | Blue
LEFT ANTI JOIN
Left anti join mengembalikan baris dari tabel kiri yang tidak punya pasangan di tabel kanan. Tidak ada keyword khusus untuk ini: lakukan LEFT JOIN dan ambil baris yang sisi kanannya bernilai NULL.
-- Input: the authors and books tables above
SELECT a.name
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.author_id IS NULL;
-- Output:
-- name
-- ------
-- Asimov
RIGHT ANTI JOIN
Right anti join mengembalikan baris dari tabel kanan yang tidak punya pasangan di tabel kiri.
-- Input: the authors and books tables above
SELECT b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;
-- Output:
-- title
-- -------
-- Beowulf
Aturan yang perlu diketahui
-
NULL tidak pernah cocok dengan apa pun, bahkan dengan NULL lainnya.
author_idBeowulf yang NULL tidak cocok dengan penulis mana pun, jadi hanya join yang mempertahankan buku tanpa pasangan yang mengembalikannya. - Sebuah baris muncul sekali untuk setiap baris yang cocok dengannya. Butler muncul dua kali di atas karena ia cocok dengan dua buku. Jika Anda lalu menjumlahkan kolom dari tabel authors, nilainya terhitung dua kali.
-
Pada outer join, kondisi untuk tabel lainnya harus ditaruh di
ON. Jika diWHERE, kondisi itu berjalan setelah join dan membuang baris NULL yang baru saja dipertahankan join:
-- Input: the authors and books tables above
-- Only Butler is left: the WHERE drops every author whose title is NULL
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.title LIKE 'K%';
-- Output:
-- name | title
-- -------+--------
-- Butler | Kindred
-- Every author is kept, with a book only where it starts with K
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id AND b.title LIKE 'K%';
-- Output:
-- name | title
-- --------+--------
-- Le Guin | NULL
-- Butler | Kindred
-- Asimov | NULL
Sintaks alternatif
MySQL tidak punya FULL OUTER JOIN. Sebagai gantinya, gabungkan LEFT JOIN dengan baris tanpa pasangan dari RIGHT JOIN:
-- MySQL
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a LEFT JOIN books b ON a.id = b.author_id
UNION ALL
SELECT a.name, b.title
FROM authors a RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Parable of the Sower
-- Butler | Kindred
-- Asimov | NULL
-- NULL | Beowulf
SQLite sebelum versi 3.39 tidak punya RIGHT JOIN atau FULL OUTER JOIN: tukar urutan tabel dan gunakan LEFT JOIN.
Anti join juga bisa ditulis dengan NOT EXISTS. Hindari NOT IN untuk ini: jika subquery mengembalikan satu NULL saja, seperti author_id Beowulf di sini, NOT IN tidak mengembalikan apa pun:
-- Input: the authors and books tables above
SELECT a.name
FROM authors a
WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);
-- Output:
-- name
-- ------
-- Asimov
SELECT a.name
FROM authors a
WHERE a.id NOT IN (SELECT author_id FROM books);
-- Output:
-- name
-- ----
-- (0 rows)
Jika kolom yang dicocokkan punya nama yang sama di kedua tabel, Anda bisa menulis JOIN books USING (author_id) sebagai pengganti kondisi ON, kecuali di SQL Server.
Kode lama sering menuliskan tabel dengan koma dan menaruh pencocokan di WHERE. Itu adalah INNER JOIN yang ditulis dengan cara lain. Hilangkan WHERE-nya, maka hasilnya menjadi CROSS JOIN:
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a, books b
WHERE a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
Kapan sebaiknya menggunakan SQL JOIN?
Gunakan join setiap kali data yang Anda butuhkan tersebar di beberapa tabel. Join mana yang dipakai bergantung pada apa yang harus terjadi pada baris yang tidak cocok, dan sebagian besar kasus termasuk salah satu dari lima berikut.
Mengambil detail dari tabel terkait
Baris karyawan menyimpan ID departemen, tetapi laporan membutuhkan nama departemennya. INNER JOIN mengambilnya, dan membuang baris yang tidak punya data untuk dicari.
Mempertahankan baris tanpa pasangan
Jika data terkait bersifat opsional, misalnya karyawan yang belum ditempatkan di departemen, INNER JOIN akan diam-diam menyembunyikannya. LEFT JOIN mempertahankan setiap baris dari tabel utama dan mengisi kekosongan dengan NULL.
Menampilkan setiap item, termasuk yang kosong
Laporan sering membutuhkan setiap departemen, kategori, atau pelanggan, termasuk yang belum berisi apa pun. Jadikan tabel itu sebagai tabel yang semua barisnya dipertahankan: di urutan pertama dengan LEFT JOIN, atau di urutan kedua dengan RIGHT JOIN.
Menemukan baris tanpa pasangan
Pelanggan yang tidak pernah memesan, produk yang tidak pernah terjual, karyawan tanpa departemen: anti join menemukan yang hilang, sehingga juga berguna untuk mengecek kualitas data.
Membuat setiap kombinasi
CROSS JOIN menghasilkan baris yang belum ada: setiap ukuran dalam setiap warna, setiap karyawan untuk setiap pelatihan, atau setiap toko pada setiap hari dalam laporan.
Contoh nyata SQL JOIN
Mahasiswa dan mata kuliahnya
Relasi many-to-many membutuhkan dua INNER JOIN melalui tabel penghubung yang mengaitkan mahasiswa dengan mata kuliah. Mei tidak terdaftar di mata kuliah mana pun, jadi ia tidak ditampilkan:
-- Input: students
-- id | name
-- ---+------
-- 1 | Amara
-- 2 | Lucas
-- 3 | Mei
-- Input: enrollments
-- student_id | course_id | grade
-- -----------+-----------+------
-- 1 | 10 | A
-- 1 | 20 | B
-- 2 | 10 | B
-- Input: courses
-- id | course_name
-- ---+------------
-- 10 | Databases
-- 20 | Statistics
SELECT s.name, c.course_name, e.grade
FROM students s
INNER JOIN enrollments e ON s.id = e.student_id
INNER JOIN courses c ON e.course_id = c.id;
-- Output:
-- name | course_name | grade
-- ------+-------------+------
-- Amara | Databases | A
-- Amara | Statistics | B
-- Lucas | Databases | B
Pengguna dengan profil opsional
Tidak semua pengguna sudah mengisi profil. LEFT JOIN mempertahankan semuanya:
-- Input: users
-- id | username
-- ---+---------
-- 1 | rosa_m
-- 2 | devon
-- 3 | kai_l
-- Input: user_profiles
-- user_id | bio
-- --------+------------------------
-- 1 | Data engineer in Lisbon
-- 3 | Loves SQL puzzles
SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;
-- Output:
-- username | bio
-- ---------+------------------------
-- rosa_m | Data engineer in Lisbon
-- devon | NULL
-- kai_l | Loves SQL puzzles
Jumlah pesanan untuk setiap pelanggan
Untuk menyertakan pelanggan tanpa pesanan, lakukan LEFT JOIN dari customers. Lalu hitung kolom dari tabel orders: COUNT(*) ikut menghitung baris buatan LEFT JOIN untuk Omar, sehingga ia akan mendapat 1, bukan 0:
-- Input: customers
-- id | name
-- ---+------------
-- 1 | Harper Lee
-- 2 | Omar Haddad
-- 3 | Yuki Tanaka
-- Input: orders
-- id | customer_id | total
-- ----+-------------+-------
-- 101 | 1 | 59.90
-- 102 | 3 | 120.00
-- 103 | 1 | 14.50
-- Wrong: COUNT(*) counts Omar's NULL row
SELECT c.name, COUNT(*) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;
-- Output:
-- name | order_count
-- ------------+------------
-- Harper Lee | 2
-- Omar Haddad | 1
-- Yuki Tanaka | 1
-- Right: COUNT(o.id) skips it
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;
-- Output:
-- name | order_count
-- ------------+------------
-- Harper Lee | 2
-- Omar Haddad | 0
-- Yuki Tanaka | 1
Invoice dan pembayaran yang tidak cocok
FULL OUTER JOIN yang hanya mempertahankan baris tanpa pasangan bisa mencocokkan dua daftar sekaligus dalam satu query: invoice 502 belum dibayar, dan pembayaran 9003 ditujukan untuk invoice yang tidak ada:
-- Input: invoices
-- invoice_id | amount
-- -----------+-------
-- 501 | 120.00
-- 502 | 75.50
-- 503 | 300.00
-- Input: payments
-- payment_id | invoice_id | paid
-- -----------+------------+-------
-- 9001 | 501 | 120.00
-- 9002 | 503 | 300.00
-- 9003 | 504 | 42.00
-- MySQL has no FULL OUTER JOIN: use the UNION ALL form under "Alternative syntax"
SELECT i.invoice_id, i.amount, p.payment_id, p.invoice_id AS paid_invoice
FROM invoices i
FULL OUTER JOIN payments p ON p.invoice_id = i.invoice_id
WHERE i.invoice_id IS NULL OR p.payment_id IS NULL;
-- Output:
-- invoice_id | amount | payment_id | paid_invoice
-- -----------+--------+------------+-------------
-- 502 | 75.50 | NULL | NULL
-- NULL | NULL | 9003 | 504
Pendapatan harian setiap toko, termasuk hari tanpa penjualan
Lakukan CROSS JOIN antara toko dan hari untuk mendapatkan satu baris per toko per hari, lalu LEFT JOIN data penjualan ke grid tersebut. York tidak menjual apa pun pada 6 Oktober, tetapi tetap mendapat baris:
-- Input: stores
-- store
-- -----
-- Leeds
-- York
-- Input: days
-- day
-- ----------
-- 2026-10-05
-- 2026-10-06
-- Input: sales
-- store | day | amount
-- ------+------------+-------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York | 2026-10-05 | 220.00
SELECT st.store, d.day, COALESCE(SUM(s.amount), 0.00) AS takings
FROM stores st
CROSS JOIN days d
LEFT JOIN sales s ON s.store = st.store AND s.day = d.day
GROUP BY st.store, d.day
ORDER BY st.store, d.day;
-- Output:
-- store | day | takings
-- ------+------------+--------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York | 2026-10-05 | 220.00
-- York | 2026-10-06 | 0.00
Bacaan lanjutan tentang SQL JOIN
Di blog ini:
- Cheat sheet SQL JOIN: semua jenis join beserta diagramnya dalam satu halaman
- MySQL FULL OUTER JOIN: cara lain menyiasati tidak adanya FULL OUTER JOIN di MySQL
- Panduan join MySQL: join di MySQL, dengan contoh
- Panduan join SQL Server: join di T-SQL
- PostgreSQL CROSS JOIN: kegunaan lain query untuk setiap kombinasi
- Panduan relasi database: bagaimana tabel yang Anda join saling berhubungan
- Relasi one-to-one, panduan relasi one-to-many, dan panduan relasi many-to-many: join yang dibutuhkan setiap relasi
- Panduan SQL COALESCE: mengganti NULL yang ditinggalkan outer join
Dokumentasi resmi:
- PostgreSQL: joined tables
- MySQL: klausa JOIN
- SQLite: klausa FROM dan join
- SQL Server: klausa FROM plus JOIN
Beekeeper Studio Adalah GUI Database Gratis & Open Source
Tool query & editor SQL terbaik yang pernah saya pakai. Semua yang saya butuhkan untuk mengelola database ada di sini. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio cepat, intuitif, dan mudah digunakan. Beekeeper mendukung banyak sekali database, dan berjalan mulus di Windows, Mac, dan Linux.
Apa Kata Pengguna tentang Beekeeper Studio
"Beekeeper Studio sepenuhnya menggantikan workflow SQL lama saya. Cepat, intuitif, dan membuat kerja dengan database kembali menyenangkan."
"Saya sudah mencoba banyak GUI database, tetapi Beekeeper punya keseimbangan sempurna antara fitur dan kesederhanaan. Langsung jalan."