Cara JOIN Lebih dari Dua Tabel di SQL Tanpa Bikin Duplikat
Blog/Tutorial SQL/Cara JOIN Lebih dari Dua Tabel di SQL Tanpa Bikin Duplikat

Cara JOIN Lebih dari Dua Tabel di SQL Tanpa Bikin Duplikat

BimaBima
·20 Februari 2026·10 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

JOIN lebih dari dua tabel di SQL dilakukan dengan menumpuk klausa JOIN secara berurutan, di mana setiap JOIN baru menyambung ke hasil gabungan sebelumnya. Masalah paling sering muncul saat salah satu tabel punya relasi one-to-many, karena satu baris bisa berlipat jadi beberapa baris dan bikin angka SUM jadi berkali lipat. Solusinya: cek jumlah baris tiap kali nambah JOIN, dan agregat tabel yang one-to-many jadi satu baris per kunci lewat subquery sebelum digabung.

Nambah JOIN keempat, total omzet tiba-tiba naik jadi tiga kali lipat. Datanya gak berubah. Yang berubah cuma jumlah barisnya.

Ini bug SQL yang paling mahal, soalnya dia gak ngasih error. Query-nya jalan mulus, angkanya keluar, dan kamu baru sadar salah pas laporan udah dikirim ke bos.

Di bawah ini penyebabnya, cara ngedeteksinya dalam 10 detik, dan cara benerinnya.

Gimana cara JOIN lebih dari dua tabel di SQL?

Caranya numpuk klausa JOIN satu per satu. Tiap JOIN baru nyambung ke hasil gabungan sebelumnya, bukan ke tabel awal doang.

SELECT ...
FROM tabel_a a
JOIN tabel_b b ON b.a_id = a.id
JOIN tabel_c c ON c.b_id = b.id
JOIN tabel_d d ON d.c_id = c.id;

Database bacanya dari atas ke bawah. tabel_c boleh nyambung ke a atau ke b — dua-duanya udah tersedia di titik itu.

Yang gak boleh: nyambung ke tabel yang belum disebut di baris sebelumnya.

Contoh kasus: 4 tabel di toko_berkah

Struktur datanya:

  • kota — daftar kota
  • pelanggan — nyambung ke kota
  • pesanan — nyambung ke pelanggan, punya kolom ongkir
  • pesanan_item — nyambung ke pesanan, satu pesanan bisa punya banyak item

Bikin tabelnya:

CREATE TABLE kota (
    kota_id   INT PRIMARY KEY,
    nama_kota VARCHAR(50) NOT NULL
);

CREATE TABLE pelanggan (
    pelanggan_id INT PRIMARY KEY,
    nama         VARCHAR(100) NOT NULL,
    kota_id      INT
);

CREATE TABLE pesanan (
    pesanan_id   INT PRIMARY KEY,
    pelanggan_id INT,
    tanggal      DATE,
    ongkir       DECIMAL(12,2)
);

CREATE TABLE pesanan_item (
    item_id     INT PRIMARY KEY,
    pesanan_id  INT,
    nama_produk VARCHAR(100),
    qty         INT,
    harga       DECIMAL(12,2)
);

Isi datanya:

INSERT INTO kota (kota_id, nama_kota) VALUES
(1, 'Bandung'),
(2, 'Cimahi');

INSERT INTO pelanggan (pelanggan_id, nama, kota_id) VALUES
(10, 'Warung Bu Siti',  1),
(11, 'Toko Pak Anwar',  2);

INSERT INTO pesanan (pesanan_id, pelanggan_id, tanggal, ongkir) VALUES
(100, 10, '2026-01-05', 20000),
(101, 11, '2026-01-06', 15000);

INSERT INTO pesanan_item (item_id, pesanan_id, nama_produk, qty, harga) VALUES
(1, 100, 'Beras 5kg',       2, 78000),
(2, 100, 'Minyak Goreng 2L',1, 36000),
(3, 100, 'Kopi Gayo 250g',  1, 65000),
(4, 101, 'Gula Aren 500g',  3, 28000);

Perhatiin: pesanan 100 punya 3 item, tapi ongkirnya cuma satu — Rp 20.000, dibayar sekali.

Query yang salah

SELECT
    k.nama_kota,
    SUM(i.qty * i.harga) AS omzet,
    SUM(p.ongkir)        AS total_ongkir
FROM pesanan p
JOIN pelanggan pl   ON pl.pelanggan_id = p.pelanggan_id
JOIN kota k         ON k.kota_id       = pl.kota_id
JOIN pesanan_item i ON i.pesanan_id    = p.pesanan_id
GROUP BY k.nama_kota;

Hasilnya:

nama_kotaomzettotal_ongkir
Bandung257.00060.000
Cimahi84.00015.000

Omzet Bandung bener. Tapi ongkirnya Rp 60.000, padahal aslinya cuma Rp 20.000.

Kenapa? Waktu pesanan di-JOIN ke pesanan_item, satu baris pesanan berlipat jadi 3 baris — satu buat tiap item. Kolom ongkir ikut kegandain, dan SUM ngitung 20.000 sebanyak tiga kali.

Selisih Rp 40.000 di data mainan ini. Di data produksi 50.000 pesanan? Bisa ratusan juta.

Gimana cara ngedeteksi baris kegandain?

Cara paling cepat: hitung barisnya tiap kali nambah JOIN.

SELECT COUNT(*) FROM pesanan;
-- 2

SELECT COUNT(*) FROM pesanan p
JOIN pelanggan pl ON pl.pelanggan_id = p.pelanggan_id;
-- 2  (aman, gak nambah)

SELECT COUNT(*) FROM pesanan p
JOIN pelanggan pl   ON pl.pelanggan_id = p.pelanggan_id
JOIN pesanan_item i ON i.pesanan_id    = p.pesanan_id;
-- 4  (naik! ini biang keroknya)

Angka naik dari 2 ke 4 padahal kamu cuma mau nambah kolom nama produk. Itu sinyalnya.

Cari kunci yang gak unik

SELECT pesanan_id, COUNT(*) AS jumlah_baris
FROM pesanan_item
GROUP BY pesanan_id
HAVING COUNT(*) > 1;

Keluar: pesanan_id = 100, jumlah baris 3. Ketemu.

Cara benerin: agregat dulu, baru JOIN

Kalau tabelnya one-to-many, ubah dia jadi one-to-one duluan. Bungkus di subquery, agregat pakai GROUP BY, baru gabungin.

SELECT
    k.nama_kota,
    SUM(item.omzet)  AS omzet,
    SUM(p.ongkir)    AS total_ongkir
FROM pesanan p
JOIN pelanggan pl ON pl.pelanggan_id = p.pelanggan_id
JOIN kota k       ON k.kota_id       = pl.kota_id
JOIN (
    SELECT pesanan_id, SUM(qty * harga) AS omzet
    FROM pesanan_item
    GROUP BY pesanan_id
) item ON item.pesanan_id = p.pesanan_id
GROUP BY k.nama_kota;

Subquery-nya nge-ringkes pesanan_item jadi satu baris per pesanan, dengan omzetnya udah dijumlah duluan.

Hasilnya sekarang:

nama_kotaomzettotal_ongkir
Bandung257.00020.000
Cimahi84.00015.000

Ongkirnya bener. Omzetnya juga tetap bener.

Versi CTE, biar lebih enak dibaca

WITH omzet_per_pesanan AS (
    SELECT pesanan_id, SUM(qty * harga) AS omzet
    FROM pesanan_item
    GROUP BY pesanan_id
)
SELECT
    k.nama_kota,
    SUM(o.omzet)  AS omzet,
    SUM(p.ongkir) AS total_ongkir
FROM pesanan p
JOIN pelanggan pl        ON pl.pelanggan_id = p.pelanggan_id
JOIN kota k              ON k.kota_id       = pl.kota_id
JOIN omzet_per_pesanan o ON o.pesanan_id    = p.pesanan_id
GROUP BY k.nama_kota;

Sama persis hasilnya. Tapi tiap langkah punya nama, jadi orang lain bisa ngerti dalam 5 detik.

Sintaks CTE lengkapnya ada di dokumentasi WITH queries PostgreSQL.

Kesalahan umum

1. Nyampur INNER JOIN sama LEFT JOIN sembarangan

Urutannya penting. LEFT JOIN yang ditaruh setelah INNER JOIN bisa kehilangan baris yang tadinya mau kamu pertahanin.

Aturannya: tabel utama di FROM, terus LEFT JOIN semua tabel tambahan sesudahnya.

2. Nyambung ke tabel yang salah

-- Salah: item nyambung ke pelanggan, bukan ke pesanan
JOIN pesanan_item i ON i.pesanan_id = pl.pelanggan_id

Kolomnya sama-sama INT, jadi gak error. Tapi hasilnya ngaco total. Selalu baca ulang kolom di dua sisi ON.

3. SELECT * di query multi-tabel

Kolom id muncul 4 kali dengan nama sama. Bingung sendiri nanti. Tulis kolom yang kamu butuh, kasih alias yang jelas.

4. Numpuk 8 tabel dalam satu query

Lewat 5–6 tabel, optimizer mulai kesulitan nyari rencana eksekusi yang bagus, dan kamu kesulitan baca query-nya sendiri. Pecah pakai CTE.

5. Percaya sama angkanya tanpa cross-check

Total omzet dari query 4 tabel harus sama dengan total omzet dari query 1 tabel pesanan_item. Kalau beda, ada yang kegandain. Cek 30 detik, hemat sehari.

Checklist sebelum kirim laporan

  1. SELECT COUNT(*) sebelum dan sesudah tiap JOIN — naik gak?
  2. Cek kunci join pakai GROUP BY ... HAVING COUNT(*) > 1
  3. Bandingin total agregat sama query sederhana satu tabel
  4. Kolom yang cuma punya satu nilai per entitas (ongkir, diskon pesanan) — jangan di-SUM setelah JOIN one-to-many

FAQ

Berapa banyak tabel yang boleh di-JOIN dalam satu query?

Secara teknis, PostgreSQL dan SQL Server sanggup nanganin puluhan tabel dalam satu query. Tapi begitu lewat 5 sampai 6 tabel, query-nya jadi susah dibaca dan optimizer mulai kesulitan nyari rencana eksekusi terbaik. Kalau butuh lebih banyak, pecah pakai CTE supaya tiap langkah punya nama dan bisa dicek sendiri-sendiri.

Kenapa total omzet jadi berlipat setelah nambah JOIN?

Karena salah satu tabel yang kamu JOIN punya relasi one-to-many. Satu pesanan yang isinya 3 item bakal jadi 3 baris setelah di-JOIN ke tabel item. Kolom yang cuma ada satu nilai per pesanan, misalnya ongkir, jadi ikut kehitung 3 kali waktu di-SUM. Solusinya agregat tabel item dulu jadi satu baris per pesanan.

Gimana cara ngecek query saya bikin duplikat atau nggak?

Jalanin SELECT COUNT(*) sebelum dan sesudah setiap JOIN ditambahin. Kalau angkanya naik padahal kamu cuma mau nambah kolom deskriptif, berarti ada relasi one-to-many yang bikin baris berlipat. Cara kedua, cek kunci join-nya pakai GROUP BY kolom_kunci HAVING COUNT(*) > 1.

Apakah urutan JOIN mempengaruhi hasil?

Buat INNER JOIN, urutannya gak ngubah hasil akhir, soalnya optimizer bebas nyusun ulang. Tapi buat LEFT JOIN, urutannya penting banget. LEFT JOIN yang ditaruh setelah INNER JOIN bisa kehilangan baris yang tadinya mau dipertahankan. Taruh tabel utama di FROM, terus LEFT JOIN semua tabel tambahan sesudahnya.

Lebih bagus pakai subquery atau CTE buat JOIN banyak tabel?

CTE lebih enak dibaca kalau query-nya panjang, soalnya tiap langkah punya nama dan bisa dites terpisah. Subquery lebih pendek buat kasus sederhana yang cuma butuh satu agregasi. Dari sisi kecepatan, di PostgreSQL modern dan SQL Server keduanya biasanya menghasilkan rencana eksekusi yang mirip.

Penutup

Tiga hal yang nyelametin laporan kamu:

  1. Baris berlipat gak pernah kasih error. Cek COUNT(*) tiap nambah JOIN.
  2. Tabel one-to-many? Agregat dulu di subquery, baru gabungin.
  3. Kolom kayak ongkir dan diskon pesanan cuma boleh di-SUM setelah barisnya udah gak kegandain.

Semua query di atas bisa langsung kamu copy dan jalanin. Coba dua-duanya, terus liat selisih ongkirnya sendiri.

Mau latihan JOIN bertingkat sama data yang lebih gede? Ada di halaman fungsi JOIN. Istilah one-to-many aku jelasin di glosarium relasi one-to-many.

Lanjut baca: EXISTS SQL — cara ngecek keberadaan data tanpa bikin baris berlipat sama sekali.

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

7 SQL Client Gratis Terbaik buat Analis Data (2026)
Tutorial SQL
10 Juli 2026•9 menit baca

7 SQL Client Gratis Terbaik buat Analis Data (2026)

Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.

BimaBima
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Tutorial SQL
7 Juli 2026•9 menit baca

SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim

Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.

BimaBima
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Tutorial SQL
4 Juli 2026•11 menit baca

Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya

Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.

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.

Copyright © 2026 - All rights reserved

LINKS
SupportPricingDatasetBlogAffiliates
LEGAL
Terms of servicesPrivacy policy
Ngulik Data
DatasetLeaderboardBlogStore