TL;DR
SELF JOIN adalah JOIN yang nggabungin satu tabel ke salinan dirinya sendiri, pakai dua alias beda. Teknik ini dipakai kalau datanya saling nyambung di dalam satu tabel. Contohnya struktur atasan-bawahan karyawan, bandingin omzet bulan ini vs bulan lalu, atau nyari transaksi duplikat. Sintaksnya sama kayak JOIN biasa, cuma tabel kiri dan kanannya namanya sama.
SELF JOIN di SQL itu JOIN yang nggabungin satu tabel ke dirinya sendiri. Bukan dua tabel beda, tapi tabel yang sama dipanggil dua kali.
Kedengeran aneh sih di awal. Buat apa tabel digabung ke dirinya sendiri?
Soalnya ada data yang saling nyambung di dalam satu tabel yang sama. Nama atasan nyimpen di tabel karyawan yang sama kayak bawahannya. Omzet bulan lalu ada di baris lain di tabel yang sama. Buat kasus kayak gini, self join satu-satunya jalan. Kalau kamu belum kenal JOIN sama sekali, pahami dulu perbedaan jenis JOIN di SQL sebelum lanjut.
Apa itu self join di SQL?
Self join adalah query yang nggabungin sebuah tabel dengan salinan tabel itu sendiri. Kamu kasih dua alias beda ke tabel yang sama, terus JOIN keduanya kayak dua tabel terpisah. Hasilnya, kamu bisa bandingin satu baris dengan baris lain di dalam tabel yang sama.
Trik mikirnya gampang kok. Anggap ada dua salinan tabel yang sama.
Salinan pertama kamu kasih nama a, salinan kedua kamu kasih nama b. Buat database, a dan b itu dua tabel beda, padahal isinya identik. Nah, dari situ kamu bisa cocokin baris di a sama baris di b.
Nggak ada sintaks khusus buat self join. Kamu tetap pakai JOIN biasa. Bedanya cuma satu: tabel di kiri dan kanan JOIN namanya sama.
Kenapa self join wajib pakai alias?
Alias wajib soalnya kamu manggil satu tabel dua kali dalam query yang sama. Tanpa alias, database bingung: kolom nama yang kamu maksud itu dari salinan yang mana? Alias kayak a dan b kasih nama beda ke tiap salinan, jadi setiap kolom punya alamat yang jelas.
Contoh kesalahan yang paling sering muncul.
-- ERROR: nama tabel "karyawan" ambigu
SELECT nama, id_atasan
FROM karyawan
JOIN karyawan ON id_atasan = id_karyawan;
Query di atas error, atau minimal hasilnya kacau. Database nggak tau nama itu punya karyawan yang mana. Begitu kamu kasih alias, semua jadi jelas.
-- BENAR: tiap salinan punya alias
SELECT b.nama, a.nama
FROM karyawan b
JOIN karyawan a ON b.id_atasan = a.id_karyawan;
Aturannya simpel deh. Begitu ketemu self join, langsung kasih alias ke dua-duanya. Jangan tunggu error dulu baru nambahin.
5 contoh self join praktis
Semua contoh di bawah pakai data toko fiktif. Query-nya udah aku tes di SQLite, jadi tinggal copy-paste. Kalau kamu pakai MySQL atau PostgreSQL, sintaks intinya sama persis.
Contoh 1: struktur atasan-bawahan karyawan
Ini kasus klasik yang cuma bisa dikerjain self join. Toko Berkah punya satu tabel karyawan. Kolom id_atasan nunjuk ke id_karyawan orang lain di tabel yang sama.
CREATE TABLE karyawan (
id_karyawan INTEGER PRIMARY KEY,
nama TEXT,
jabatan TEXT,
id_atasan INTEGER,
gaji INTEGER
);
INSERT INTO karyawan VALUES
(1,'Bu Sari','Owner',NULL,25000000),
(2,'Andi','Manajer Toko',1,12000000),
(3,'Rina','Kasir',2,4500000),
(4,'Budi','Kasir',2,4000000),
(5,'Dewi','Admin Gudang',2,13000000),
(6,'Joko','Staf Gudang',5,3500000);
Kamu mau tampilin tiap karyawan sama nama atasannya. Nama atasan ada di baris lain, di tabel yang sama. Self join yang ngambilnya.
SELECT b.nama AS karyawan, b.jabatan, a.nama AS atasan
FROM karyawan b
LEFT JOIN karyawan a ON b.id_atasan = a.id_karyawan
ORDER BY b.id_karyawan;
Hasilnya:
| karyawan | jabatan | atasan |
|---|---|---|
| Bu Sari | Owner | (kosong) |
| Andi | Manajer Toko | Bu Sari |
| Rina | Kasir | Andi |
| Budi | Kasir | Andi |
| Dewi | Admin Gudang | Andi |
| Joko | Staf Gudang | Dewi |
Perhatiin b dan a. Salinan b itu si bawahan, salinan a itu atasannya. Aku pakai LEFT JOIN biar Bu Sari tetap muncul walau atasannya kosong.
Contoh 2: bandingin omzet bulan ini vs bulan lalu
Tabel penjualan Keripik Juara nyimpen omzet per bulan, satu baris satu bulan. Kamu mau tau naik-turunnya tiap bulan. Angka bulan lalu ada di baris atasnya, di tabel yang sama.
CREATE TABLE penjualan_bulanan (
id_bulan INTEGER,
bulan TEXT,
total_omzet INTEGER
);
INSERT INTO penjualan_bulanan VALUES
(1,'Januari', 42000000),
(2,'Februari',38000000),
(3,'Maret', 51000000),
(4,'April', 47000000),
(5,'Mei', 55000000);
Kunci join-nya di sini: cocokin baris bulan ini sama baris yang id_bulan-nya kurang satu.
SELECT ini.bulan,
ini.total_omzet AS omzet_bulan_ini,
lalu.total_omzet AS omzet_bulan_lalu,
ini.total_omzet - lalu.total_omzet AS selisih
FROM penjualan_bulanan ini
JOIN penjualan_bulanan lalu
ON ini.id_bulan = lalu.id_bulan + 1
ORDER BY ini.id_bulan;
Hasilnya:
| bulan | omzet_bulan_ini | omzet_bulan_lalu | selisih |
|---|---|---|---|
| Februari | 38.000.000 | 42.000.000 | -4.000.000 |
| Maret | 51.000.000 | 38.000.000 | 13.000.000 |
| April | 47.000.000 | 51.000.000 | -4.000.000 |
| Mei | 55.000.000 | 47.000.000 | 8.000.000 |
Januari nggak muncul, soalnya nggak ada bulan sebelumnya buat dibandingin. Itu wajar. Buat kasus geser baris kayak gini, ada cara yang lebih rapi pakai window function lewat fungsi LAG. Tapi self join tetap jalan dan gampang dipahami buat pemula.
Contoh 3: cari transaksi duplikat
Kadang sistem kasir nge-save transaksi dua kali gara-gara dobel klik. Pelanggan sama, nominal sama, selisih waktunya cuma beberapa detik. Buat nangkepnya, kamu bandingin tiap transaksi sama transaksi lain di tabel yang sama.
CREATE TABLE transaksi (
id_transaksi INTEGER PRIMARY KEY,
id_pelanggan INTEGER,
nominal INTEGER,
waktu TEXT
);
INSERT INTO transaksi VALUES
(101, 7, 150000,'2026-07-01 10:15:00'),
(102, 7, 150000,'2026-07-01 10:15:30'),
(103, 9, 89000, '2026-07-01 11:00:00'),
(104,12, 200000,'2026-07-01 12:30:00'),
(105,12, 200000,'2026-07-01 12:30:20'),
(106, 9, 89000, '2026-07-02 09:00:00');
Query-nya cocokin pelanggan dan nominal yang sama, tapi id transaksinya beda.
SELECT a.id_transaksi AS trx_1,
b.id_transaksi AS trx_2,
a.id_pelanggan, a.nominal
FROM transaksi a
JOIN transaksi b
ON a.id_pelanggan = b.id_pelanggan
AND a.nominal = b.nominal
AND a.id_transaksi < b.id_transaksi
AND ABS(strftime('%s', b.waktu) - strftime('%s', a.waktu)) < 60;
Hasilnya nangkep dua pasang duplikat:
| trx_1 | trx_2 | id_pelanggan | nominal |
|---|---|---|---|
| 101 | 102 | 7 | 150.000 |
| 104 | 105 | 12 | 200.000 |
Ada dua trik penting di sini. Syarat a.id_transaksi < b.id_transaksi bikin tiap pasangan cuma muncul sekali, bukan bolak-balik. Terus syarat selisih waktu di bawah 60 detik mastiin transaksi 103 dan 106 nggak ketangkep, walau pelanggan dan nominalnya sama, soalnya jaraknya sehari.
Catatan dialek: strftime('%s', ...) itu punya SQLite. Di MySQL pakai ABS(TIMESTAMPDIFF(SECOND, a.waktu, b.waktu)). Di PostgreSQL pakai ABS(EXTRACT(EPOCH FROM b.waktu - a.waktu)).
Contoh 4: cari pasangan pelanggan dari kota yang sama
Toko Berkah mau bikin promo "ajak tetangga". Kamu perlu daftar pasangan pelanggan yang tinggal di kota yang sama. Datanya semua di satu tabel pelanggan.
CREATE TABLE pelanggan (
id_pelanggan INTEGER PRIMARY KEY,
nama TEXT,
kota TEXT
);
INSERT INTO pelanggan VALUES
(1,'Wati','Bandung'),
(2,'Toni','Surabaya'),
(3,'Sinta','Bandung'),
(4,'Rudi','Surabaya'),
(5,'Nina','Bandung');
SELECT a.nama AS pelanggan_1,
b.nama AS pelanggan_2,
a.kota
FROM pelanggan a
JOIN pelanggan b
ON a.kota = b.kota
AND a.id_pelanggan < b.id_pelanggan
ORDER BY a.kota;
Hasilnya:
| pelanggan_1 | pelanggan_2 | kota |
|---|---|---|
| Wati | Sinta | Bandung |
| Wati | Nina | Bandung |
| Sinta | Nina | Bandung |
| Toni | Rudi | Surabaya |
Syarat a.id_pelanggan < b.id_pelanggan penting banget. Tanpa itu, Wati bakal dipasangin sama dirinya sendiri, dan tiap pasangan muncul dua kali. Syarat ini bikin daftarnya bersih.
Contoh 5: karyawan yang gajinya lebih besar dari atasannya
Balik ke tabel karyawan tadi. Kamu mau ngecek ada nggak bawahan yang gajinya malah lebih tinggi dari atasan langsungnya. Self join bandingin gaji karyawan sama gaji atasannya di baris lain.
SELECT b.nama AS karyawan, b.gaji,
a.nama AS atasan, a.gaji AS gaji_atasan
FROM karyawan b
JOIN karyawan a ON b.id_atasan = a.id_karyawan
WHERE b.gaji > a.gaji;
Hasilnya:
| karyawan | gaji | atasan | gaji_atasan |
|---|---|---|---|
| Dewi | 13.000.000 | Andi | 12.000.000 |
Dewi si Admin Gudang gajinya 13 juta, lebih tinggi dari Andi manajernya yang 12 juta. Tanpa self join, kamu bakal susah nemu anomali kayak gini, soalnya gaji atasan dan bawahan ada di kolom yang sama, cuma beda baris.
Kesalahan umum saat pakai self join
Ada tiga jebakan yang paling sering bikin hasil self join salah. Lupa alias, lupa syarat anti-duplikat, dan salah arah perbandingan. Sekali kamu hafal polanya, self join jadi gampang.
Pertama, lupa kasih alias. Ini bikin database bingung kolomnya dari salinan mana, dan query-nya langsung error. Selalu kasih dua alias beda, walau ngetiknya cuma satu huruf.
Kedua, lupa syarat a.id < b.id pas nyari pasangan. Tanpa itu, tiap baris kepasang sama dirinya sendiri, dan pasangan yang sama muncul dua kali. Hasilnya jadi dobel.
Ketiga, ketuker arah perbandingan. Di kasus atasan-bawahan, pastiin b.id_atasan = a.id_karyawan, bukan kebalik. Kalau ketuker, kamu malah dapet bawahan di kolom atasan.
Kalau query-nya udah numpuk dan susah dibaca, self join juga enak dipecah pakai CTE lewat WITH. Itu bikin tiap langkah kebaca satu-satu.
FAQ
Apa bedanya self join sama JOIN biasa?
JOIN biasa nggabungin dua tabel yang beda, misalnya tabel pesanan sama tabel pelanggan. Self join nggabungin satu tabel ke salinan dirinya sendiri. Sintaksnya sama persis, kamu tetap pakai JOIN ... ON. Bedanya cuma tabel kiri dan kanannya namanya sama, jadi wajib dikasih dua alias beda.
Apakah self join harus pakai INNER JOIN?
Nggak harus. Self join bisa pakai INNER JOIN atau LEFT JOIN, tergantung kebutuhan. Kalau kamu mau baris yang pasangannya kosong tetap muncul, kayak Owner yang nggak punya atasan, pakai LEFT JOIN. Kalau cuma butuh yang ada pasangannya, INNER JOIN udah cukup.
Kenapa hasil self join aku dobel?
Biasanya gara-gara lupa syarat pembatas di klausa ON. Pas nyari pasangan di tabel yang sama, tambahin a.id < b.id biar tiap pasangan cuma muncul sekali. Tanpa syarat itu, tiap baris kepasang sama dirinya sendiri dan pasangan yang sama keluar dua kali dengan urutan kebalik.
Apakah self join lambat kalau datanya banyak?
Bisa lambat kalau tabelnya besar dan kolom yang di-join nggak punya index. Soalnya database bandingin tiap baris sama baris lain. Buat data ratusan ribu baris ke atas, pastiin kolom di klausa ON udah di-index. Buat data toko kecil sampai menengah, kecepatannya nggak jadi masalah.
Bisa nggak self join lebih dari dua kali?
Bisa. Kamu boleh nge-join tabel yang sama tiga kali atau lebih, tinggal tambah alias lagi kayak a, b, c. Ini kepake buat lacak hierarki bertingkat, misalnya karyawan, atasannya, sampai atasan dari atasannya. Tapi makin banyak salinan, query-nya makin susah dibaca, jadi pertimbangin pecah pakai CTE.
Penutup
Self join kepake tiap kali data yang mau kamu bandingin ada di dalam satu tabel yang sama. Struktur atasan-bawahan, omzet antar bulan, transaksi duplikat, sampai pasangan pelanggan sekota. Kuncinya cuma dua: kasih dua alias beda, dan pikirin sebagai dua salinan tabel yang identik.
Sebelum kulik self join lebih jauh, pastiin dulu dasar JOIN kamu udah mantap. Lanjut baca perbedaan jenis JOIN di SQL biar kamu paham kapan pakai INNER, LEFT, dan RIGHT.
Referensi resmi soal JOIN bisa kamu cek di dokumentasi PostgreSQL dan dokumentasi MySQL.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel Terkait
UNION vs UNION ALL di SQL: Perbedaan dan Kapan Pakai
Beda UNION dan UNION ALL cuma satu kata, tapi salah pilih bikin angka revenue kamu berubah. Ini penjelasan bedanya, kapan pakai, plus kenapa UNION ALL lebih cepat.
SQL untuk Data Analysis: 8 Skill Wajib Sebelum Apply Kerja
Checklist 8 skill SQL yang beneran dipake data analyst tiap hari, lengkap query contoh dan self-test bisa atau nggak. Fokus yang penting dulu, sisanya bisa nyusul.
Sintaks SQL Dasar: Struktur Query yang Wajib Dipahami
Urutan nulis query SQL beda sama urutan database ngejalaninnya. Paham selisih ini bikin error sintaks pemula tiba-tiba masuk akal.