Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain
Blog/Tutorial SQL/Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain

Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain

BimaBima
·14 November 2026·8 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Semi join di SQL ngambil baris dari tabel kiri yang punya minimal satu pasangan di tabel kanan, biasanya ditulis pakai EXISTS atau IN. Anti join kebalikannya, ngambil baris yang nggak punya pasangan sama sekali, ditulis pakai NOT EXISTS atau LEFT JOIN dengan cek NULL. Keduanya cuma filter baris tabel kiri dan nggak nambahin kolom dari tabel kanan.

Semi join di SQL ngambil baris dari tabel kiri yang punya pasangan di tabel kanan, tapi nggak nambahin kolom apa pun dari tabel kanan. Anti join kebalikannya, ngambil baris yang nggak punya pasangan sama sekali.

Dua istilah ini jarang muncul di menu SQL. Nggak ada keyword SEMI JOIN yang bisa kamu ketik langsung. Tapi polanya kepake tiap hari, cuma ditulis pakai EXISTS, IN, atau NOT EXISTS.

Begitu ngerti bedanya, banyak query filter yang tadinya ribet jadi jelas. Aku bahas keduanya di sini pakai contoh data toko biar kebayang.

Apa Itu Semi Join di SQL?

Semi join adalah operasi yang ngambil baris dari tabel kiri kalau ada minimal satu baris cocok di tabel kanan. Kata kuncinya: cukup satu kecocokan, dan baris kiri keluar sekali aja. Nggak ada penggandaan, nggak ada kolom tambahan dari tabel kanan.

Cara paling umum nulisnya pakai EXISTS:

SELECT p.pelanggan_id, p.nama
FROM pelanggan p
WHERE EXISTS (
  SELECT 1 FROM transaksi t
  WHERE t.pelanggan_id = p.pelanggan_id
);

Query ini jawab pertanyaan: "pelanggan mana aja yang pernah transaksi minimal sekali?" Kalau satu pelanggan punya 50 transaksi, dia tetap muncul sekali. Itu inti semi join.

Butuh penyegaran soal cara kerja subquery dan EXISTS? Cek dulu glossary subquery.

Apa Itu Anti Join di SQL?

Anti join ngambil baris dari tabel kiri yang nggak punya pasangan sama sekali di tabel kanan. Ini kebalikan persis dari semi join. Cara amannya pakai NOT EXISTS:

SELECT p.pelanggan_id, p.nama
FROM pelanggan p
WHERE NOT EXISTS (
  SELECT 1 FROM transaksi t
  WHERE t.pelanggan_id = p.pelanggan_id
);

Query ini jawab: "pelanggan mana yang belum pernah transaksi?" Berguna banget buat cari akun tidur, produk yang belum pernah kejual, atau data master yang nggak kepakai.

Anti join juga bisa ditulis pakai LEFT JOIN, aku tunjukin di bawah.

Semi Join Pakai IN, Apa Bedanya sama EXISTS?

Semi join juga bisa ditulis pakai IN. Hasilnya sama kayak EXISTS buat kasus normal. Bedanya kerasa waktu ada NULL dan waktu subquery balikin banyak baris.

-- Versi IN
SELECT p.pelanggan_id, p.nama
FROM pelanggan p
WHERE p.pelanggan_id IN (
  SELECT t.pelanggan_id FROM transaksi t
);

Buat semi join, IN aman dan gampang dibaca. Yang bahaya itu versi negatifnya, NOT IN, buat anti join. Ini tabel bandinganya:

PolaFungsiAman terhadap NULL?
EXISTSSemi joinAman
INSemi joinAman
NOT EXISTSAnti joinAman
NOT INAnti joinBahaya kalau ada NULL

Aturan amanku: EXISTS dan NOT EXISTS buat semua kasus. Konsisten dan nggak kena jebakan NULL.

Kenapa NOT IN Bisa Bikin Query Balik Kosong?

Kalau daftar pembanding di NOT IN mengandung satu aja nilai NULL, seluruh hasil bisa jadi kosong. Ini karena NOT IN bandingin nilai satu per satu, dan ketemu NULL bikin hasilnya UNKNOWN, bukan TRUE.

Contoh jebakannya:

-- BAHAYA kalau kolom pelanggan_id di transaksi ada yang NULL
SELECT p.nama
FROM pelanggan p
WHERE p.pelanggan_id NOT IN (
  SELECT t.pelanggan_id FROM transaksi t
);

Kalau ada satu transaksi dengan pelanggan_id NULL, query di atas balik kosong total. Padahal jelas ada pelanggan yang belum transaksi.

Makanya buat anti join, pakai NOT EXISTS. Dia ngecek keberadaan baris, bukan bandingin nilai, jadi NULL nggak ngerusak hasil.

Anti Join Pakai LEFT JOIN dan IS NULL

Cara lain bikin anti join: LEFT JOIN ke tabel kanan, terus ambil baris yang kolom kanannya NULL. Baris yang nggak ketemu pasangan otomatis dapat NULL di sisi kanan.

SELECT p.pelanggan_id, p.nama
FROM pelanggan p
LEFT JOIN transaksi t
  ON t.pelanggan_id = p.pelanggan_id
WHERE t.pelanggan_id IS NULL;

Hasilnya sama persis kayak NOT EXISTS. Yang penting: kondisi join taruh di ON, dan cek NULL taruh di WHERE. Kalau kebalik, hasilnya bisa ngaco.

Pengin lebih paham beda menaruh kondisi di ON versus WHERE? Aku bahas di klausa USING vs ON pada JOIN.

Contoh Kasus: Pelanggan Tidur di toko_berkah

Di database toko_berkah, ada 1.240 pelanggan terdaftar. Aku mau cari yang belum pernah belanja sama sekali dalam 6 bulan terakhir, biar bisa dikirimin promo.

SELECT p.pelanggan_id, p.nama, p.kota
FROM pelanggan p
WHERE NOT EXISTS (
  SELECT 1 FROM transaksi t
  WHERE t.pelanggan_id = p.pelanggan_id
    AND t.tanggal >= '2026-05-01'
);

Hasilnya 318 pelanggan tidur. Artinya 25,6% dari total pelanggan terdaftar nggak transaksi selama setengah tahun. Angka ini bikin tim toko sadar database pelanggannya banyak yang nganggur.

Kalau aku balik jadi semi join (EXISTS), hasilnya 922 pelanggan aktif. Dua angka ini dari data yang sama, cuma beda arah filter. Itulah gunanya paham semi versus anti join.

Kesalahan Umum

  • Pakai NOT IN dengan kolom yang bisa NULL. Ganti ke NOT EXISTS. Ini bug paling sering di anti join.
  • Naruh kondisi NULL di ON, bukan WHERE. Di anti join LEFT JOIN, cek IS NULL harus di WHERE.
  • Pakai inner join buat filter. Kalau cuma mau cek keberadaan, inner join bisa gandain baris. Pakai semi join.
  • SELECT kolom dari tabel kanan di semi join. Semi join nggak nyediain kolom tabel kanan. Kalau butuh kolomnya, kamu sebenernya butuh JOIN biasa.

FAQ

Pertanyaan yang sering muncul soal semi join dan anti join di SQL.

Penutup

Semi join ngambil yang punya pasangan, anti join ngambil yang nggak punya. Keduanya filter baris tabel kiri, bukan gabung kolom.

Buat aman, pakai EXISTS buat semi join dan NOT EXISTS buat anti join. Hindari NOT IN kalau ada kemungkinan NULL.

Coba tulis query "pelanggan yang belum pernah beli" di databasemu sendiri pakai NOT EXISTS. Bandingin sama versi NOT IN dan lihat bedanya kalau ada NULL.

Mau lanjut? Baca USING vs ON pada JOIN SQL. Detail soal EXISTS ada di dokumentasi resmi PostgreSQL.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom
Tutorial SQL
2 November 2026•11 menit baca

Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom

Crosstab di PostgreSQL mivot data dari format panjang (baris) jadi format lebar (kolom), mirip pivot table Excel. Butuh extension tablefunc, dan ada alternatif pakai CASE atau FILTER kalau kolomnya tetap.

BimaBima
INSERT ON CONFLICT di PostgreSQL
Tutorial SQL
25 Oktober 2026•11 menit baca

INSERT ON CONFLICT di PostgreSQL

INSERT ON CONFLICT bikin PostgreSQL insert baris baru atau update yang udah ada dalam satu perintah, tanpa error gara-gara duplikat primary key.

BimaBima
7 Antipattern SQL yang Bikin Query Lambat
Tutorial SQL
18 Oktober 2026•8 menit baca

7 Antipattern SQL yang Bikin Query Lambat

Tujuh pola SQL yang diam-diam bikin query lambat, dari SELECT bintang, fungsi di kolom filter, sampai OFFSET besar. Plus cara benerin tiap satunya.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore