Cara Menggunakan Subquery di SQL (Dengan 10 Contoh Praktis)
TL;DR
Subquery itu query di dalam query. Bisa dipake di WHERE, FROM, atau SELECT. Pake buat filter, bikin tabel sementara, atau ngitung nilai buat perbandingan.
Apa Itu Subquery?
Pernah kepikiran ga, gimana caranya cari produk yang harganya di atas rata-rata? Atau gimana caranya nampilin customer yang belanja paling banyak?
Untuk kasus kayak gitu, kamu butuh yang namanya subquery. Subquery itu basically query di dalam query. Kamu nulis satu query, terus hasilnya dipake sama query luar.
Bayangin kayak gini: kamu mau beli baju yang harganya di bawah rata-rata. Pertama, kamu hitung dulu rata-rata harga semua baju. Baru setelah itu, kamu filter baju yang harganya di bawah angka itu. Nah, hitungan rata-rata itu ibarat subquery.
Kenapa Perlu Belajar Subquery?
Sebagai Data Analyst, kamu bakal sering nemuin case yang ga bisa diselesaiin cuma pake WHERE biasa. Contohnya:
- Cari transaksi di atas rata-rata
- Tampilin produk yang belum pernah kejual
- Bandingkan performa tiap cabang dengan total keseluruhan
- Filter data berdasarkan hasil agregasi tabel lain
Semua itu butuh subquery. Tanpa subquery, query kamu bakal jadi ribet banget atau bahkan ga mungkin dibikin.
Dataset yang Akan Kita Pakai
Buat tutorial ini, kita pakai dataset e-commerce lokal. Bayangin kamu jadi Data Analyst di startup marketplace Indonesia.
Tabel: produk
| id | nama_produk | kategori | harga | stok |
|---|---|---|---|---|
| 1 | Batik Pekalongan | Fashion | 185000 | 50 |
| 2 | Kopi Toraja 250g | Makanan | 75000 | 100 |
| 3 | Tas Kulit Garut | Fashion | 450000 | 25 |
| 4 | Sambal Bu Rudy | Makanan | 35000 | 200 |
| 5 | Gelang Perak Bali | Aksesoris | 250000 | 30 |
| 6 | Keripik Tempe | Makanan | 25000 | 150 |
| 7 | Kaos Dagadu | Fashion | 95000 | 80 |
| 8 | Dodol Betawi | Makanan | 45000 | 120 |
| 9 | Cincin Perak | Aksesoris | 175000 | 40 |
| 10 | Mukena Bali | Fashion | 320000 | 35 |
Tabel: transaksi
| id | tanggal | customer_id | produk_id | qty | total |
|---|---|---|---|---|---|
| 1 | 2024-01-15 | 101 | 1 | 2 | 370000 |
| 2 | 2024-01-15 | 102 | 2 | 3 | 225000 |
| 3 | 2024-01-16 | 103 | 4 | 5 | 175000 |
| 4 | 2024-01-16 | 101 | 3 | 1 | 450000 |
| 5 | 2024-01-17 | 104 | 6 | 10 | 250000 |
| 6 | 2024-01-17 | 102 | 5 | 1 | 250000 |
| 7 | 2024-01-18 | 105 | 7 | 2 | 190000 |
| 8 | 2024-01-18 | 103 | 8 | 4 | 180000 |
| 9 | 2024-01-19 | 101 | 2 | 2 | 150000 |
| 10 | 2024-01-19 | 106 | 1 | 1 | 185000 |
Tabel: customer
| id | nama | kota | tgl_daftar |
|---|---|---|---|
| 101 | Budi Santoso | Jakarta | 2023-06-15 |
| 102 | Siti Rahayu | Bandung | 2023-08-20 |
| 103 | Andi Wijaya | Surabaya | 2023-09-10 |
| 104 | Dewi Lestari | Yogyakarta | 2023-11-05 |
| 105 | Reza Pratama | Semarang | 2024-01-02 |
| 106 | Maya Putri | Jakarta | 2024-01-10 |
Jenis-Jenis Subquery
Ada 3 tempat utama di mana kamu bisa pake subquery:
- Subquery di WHERE - paling umum, buat filter data
- Subquery di FROM - bikin tabel sementara
- Subquery di SELECT - ngitung nilai buat tiap baris
Kita bahas satu-satu dengan contoh praktis ya.
Subquery di WHERE Clause
Ini jenis subquery yang paling sering dipake. Kamu pake hasil dari subquery buat filter data di query utama.
Contoh 1: Produk dengan Harga di Atas Rata-rata
Cari produk yang harganya di atas rata-rata semua produk.
SELECT nama_produk, kategori, harga
FROM produk
WHERE harga > (
SELECT AVG(harga) FROM produk
);
Cara kerjanya:
1. Subquery SELECT AVG(harga) FROM produk dijalankan dulu, hasilnya 165500
2. Query utama filter produk yang harganya > 165500
Hasil:
| nama_produk | kategori | harga |
|---|---|---|
| Batik Pekalongan | Fashion | 185000 |
| Tas Kulit Garut | Fashion | 450000 |
| Gelang Perak Bali | Aksesoris | 250000 |
| Cincin Perak | Aksesoris | 175000 |
| Mukena Bali | Fashion | 320000 |
Contoh 2: Customer dengan Total Belanja Tertinggi
Cari customer yang total belanjanya sama dengan nilai tertinggi.
SELECT c.nama, c.kota, SUM(t.total) AS total_belanja
FROM customer c
JOIN transaksi t ON c.id = t.customer_id
GROUP BY c.id, c.nama, c.kota
HAVING SUM(t.total) = (
SELECT MAX(total_per_customer)
FROM (
SELECT customer_id, SUM(total) AS total_per_customer
FROM transaksi
GROUP BY customer_id
) AS sub
);
Hasil:
| nama | kota | total_belanja |
|---|---|---|
| Budi Santoso | Jakarta | 970000 |
Di sini kita pake nested subquery. Subquery dalem ngitung total belanja per customer, subquery luar ambil nilai MAX-nya.
Contoh 3: Menggunakan IN dengan Subquery
Cari transaksi yang produknya termasuk kategori Fashion.
SELECT t.id, t.tanggal, p.nama_produk, t.total
FROM transaksi t
JOIN produk p ON t.produk_id = p.id
WHERE t.produk_id IN (
SELECT id FROM produk WHERE kategori = 'Fashion'
);
Hasil:
| id | tanggal | nama_produk | total |
|---|---|---|---|
| 1 | 2024-01-15 | Batik Pekalongan | 370000 |
| 4 | 2024-01-16 | Tas Kulit Garut | 450000 |
| 7 | 2024-01-18 | Kaos Dagadu | 190000 |
| 10 | 2024-01-19 | Batik Pekalongan | 185000 |
Contoh 4: Menggunakan NOT IN
Cari produk yang BELUM pernah terjual.
SELECT id, nama_produk, kategori
FROM produk
WHERE id NOT IN (
SELECT DISTINCT produk_id FROM transaksi
);
Hasil:
| id | nama_produk | kategori |
|---|---|---|
| 9 | Cincin Perak | Aksesoris |
| 10 | Mukena Bali | Fashion |
Ini berguna banget buat analisis produk yang ga laku.
Contoh 5: Subquery dengan ANY/ALL
Cari produk yang harganya lebih mahal dari SEMUA produk kategori Makanan.
SELECT nama_produk, kategori, harga
FROM produk
WHERE harga > ALL (
SELECT harga FROM produk WHERE kategori = 'Makanan'
);
Hasil:
| nama_produk | kategori | harga |
|---|---|---|
| Batik Pekalongan | Fashion | 185000 |
| Tas Kulit Garut | Fashion | 450000 |
| Gelang Perak Bali | Aksesoris | 250000 |
| Kaos Dagadu | Fashion | 95000 |
| Cincin Perak | Aksesoris | 175000 |
| Mukena Bali | Fashion | 320000 |
Harga tertinggi di kategori Makanan itu 75000 (Kopi Toraja). Jadi semua produk di atas 75000 masuk ke hasil.
Subquery di FROM Clause (Derived Table)
Subquery di FROM bikin tabel sementara yang bisa kamu query lagi. Ini powerful banget buat analisis yang kompleks.
Contoh 6: Ranking Customer berdasarkan Total Belanja
SELECT
ranking.nama,
ranking.kota,
ranking.total_belanja,
ranking.jumlah_transaksi
FROM (
SELECT
c.nama,
c.kota,
SUM(t.total) AS total_belanja,
COUNT(*) AS jumlah_transaksi
FROM customer c
JOIN transaksi t ON c.id = t.customer_id
GROUP BY c.id, c.nama, c.kota
) AS ranking
ORDER BY ranking.total_belanja DESC;
Hasil:
| nama | kota | total_belanja | jumlah_transaksi |
|---|---|---|---|
| Budi Santoso | Jakarta | 970000 | 3 |
| Siti Rahayu | Bandung | 475000 | 2 |
| Andi Wijaya | Surabaya | 355000 | 2 |
| Dewi Lestari | Yogyakarta | 250000 | 1 |
| Reza Pratama | Semarang | 190000 | 1 |
| Maya Putri | Jakarta | 185000 | 1 |
Contoh 7: Perbandingan dengan Total Keseluruhan
Hitung persentase kontribusi tiap kategori terhadap total penjualan.
SELECT
kategori_stats.kategori,
kategori_stats.total_penjualan,
total_stats.grand_total,
ROUND(
kategori_stats.total_penjualan * 100.0 / total_stats.grand_total,
2
) AS persentase
FROM (
SELECT
p.kategori,
SUM(t.total) AS total_penjualan
FROM produk p
JOIN transaksi t ON p.id = t.produk_id
GROUP BY p.kategori
) AS kategori_stats
CROSS JOIN (
SELECT SUM(total) AS grand_total FROM transaksi
) AS total_stats
ORDER BY persentase DESC;
Hasil:
| kategori | total_penjualan | grand_total | persentase |
|---|---|---|---|
| Fashion | 1195000 | 2425000 | 49.28 |
| Makanan | 980000 | 2425000 | 40.41 |
| Aksesoris | 250000 | 2425000 | 10.31 |
Nah, ini insight yang valuable banget. Fashion nyumbang hampir 50% total penjualan!
Subquery di SELECT Clause (Scalar Subquery)
Subquery di SELECT dipake buat ngitung nilai yang bakal muncul sebagai kolom baru di tiap baris.
Contoh 8: Menampilkan Rata-rata di Setiap Baris
Tampilin harga produk beserta rata-rata harga kategorinya.
SELECT
nama_produk,
kategori,
harga,
(
SELECT ROUND(AVG(harga), 0)
FROM produk p2
WHERE p2.kategori = p1.kategori
) AS avg_kategori,
harga - (
SELECT ROUND(AVG(harga), 0)
FROM produk p2
WHERE p2.kategori = p1.kategori
) AS selisih_dari_avg
FROM produk p1
ORDER BY kategori, harga DESC;
Hasil:
| nama_produk | kategori | harga | avg_kategori | selisih_dari_avg |
|---|---|---|---|---|
| Gelang Perak Bali | Aksesoris | 250000 | 212500 | 37500 |
| Cincin Perak | Aksesoris | 175000 | 212500 | -37500 |
| Tas Kulit Garut | Fashion | 450000 | 262500 | 187500 |
| Mukena Bali | Fashion | 320000 | 262500 | 57500 |
| Batik Pekalongan | Fashion | 185000 | 262500 | -77500 |
| Kaos Dagadu | Fashion | 95000 | 262500 | -167500 |
| Kopi Toraja 250g | Makanan | 75000 | 45000 | 30000 |
| Dodol Betawi | Makanan | 45000 | 45000 | 0 |
| Sambal Bu Rudy | Makanan | 35000 | 45000 | -10000 |
| Keripik Tempe | Makanan | 25000 | 45000 | -20000 |
Ini berguna buat liat mana produk yang overpriced atau underpriced dibanding rata-rata kategorinya.
Contoh 9: Count Related Records
Tampilin produk beserta jumlah transaksinya.
SELECT
p.nama_produk,
p.kategori,
p.harga,
(
SELECT COUNT(*)
FROM transaksi t
WHERE t.produk_id = p.id
) AS jumlah_terjual
FROM produk p
ORDER BY jumlah_terjual DESC;
Hasil:
| nama_produk | kategori | harga | jumlah_terjual |
|---|---|---|---|
| Batik Pekalongan | Fashion | 185000 | 2 |
| Kopi Toraja 250g | Makanan | 75000 | 2 |
| Tas Kulit Garut | Fashion | 450000 | 1 |
| Sambal Bu Rudy | Makanan | 35000 | 1 |
| Gelang Perak Bali | Aksesoris | 250000 | 1 |
| Keripik Tempe | Makanan | 25000 | 1 |
| Kaos Dagadu | Fashion | 95000 | 1 |
| Dodol Betawi | Makanan | 45000 | 1 |
| Cincin Perak | Aksesoris | 175000 | 0 |
| Mukena Bali | Fashion | 320000 | 0 |
Correlated Subquery
Correlated subquery itu subquery yang reference kolom dari query luar. Setiap baris di query utama, subquery-nya dijalankan ulang.
Contoh 10: Cari Transaksi Tertinggi per Customer
SELECT
t.id,
c.nama,
t.tanggal,
t.total
FROM transaksi t
JOIN customer c ON t.customer_id = c.id
WHERE t.total = (
SELECT MAX(total)
FROM transaksi t2
WHERE t2.customer_id = t.customer_id
);
Hasil:
| id | nama | tanggal | total |
|---|---|---|---|
| 4 | Budi Santoso | 2024-01-16 | 450000 |
| 6 | Siti Rahayu | 2024-01-17 | 250000 |
| 5 | Dewi Lestari | 2024-01-17 | 250000 |
| 7 | Reza Pratama | 2024-01-18 | 190000 |
| 10 | Maya Putri | 2024-01-19 | 185000 |
| 8 | Andi Wijaya | 2024-01-18 | 180000 |
Di sini, t.customer_id di subquery nge-refer ke baris yang lagi diproses di query utama.
EXISTS vs IN: Kapan Pake Yang Mana?
Kadang kamu bisa pake IN atau EXISTS buat hasil yang sama. Tapi ada perbedaan performa.
Pake IN
SELECT nama_produk
FROM produk
WHERE id IN (SELECT produk_id FROM transaksi);
Pake EXISTS
SELECT nama_produk
FROM produk p
WHERE EXISTS (
SELECT 1 FROM transaksi t WHERE t.produk_id = p.id
);
Kapan pake apa?
- EXISTS lebih cepat kalau subquery hasilnya banyak
- IN lebih cepat kalau subquery hasilnya sedikit
- EXISTS lebih aman untuk handle NULL
- IN lebih readable untuk case simple
Common Mistakes yang Harus Dihindari
Mistake 1: Subquery Return Multiple Rows Padahal Expect Single Value
-- SALAH kalau subquery return > 1 row
SELECT * FROM produk
WHERE harga = (SELECT harga FROM produk WHERE kategori = 'Fashion');
Fix: Pake IN atau agregasi function.
-- BENAR
SELECT * FROM produk
WHERE harga IN (SELECT harga FROM produk WHERE kategori = 'Fashion');
Mistake 2: Subquery Terlalu Kompleks
Kadang subquery bisa di-replace dengan JOIN yang lebih efficient.
-- Bisa jadi lambat
SELECT nama_produk
FROM produk
WHERE id IN (
SELECT produk_id FROM transaksi WHERE total > 200000
);
-- Lebih efficient pake JOIN
SELECT DISTINCT p.nama_produk
FROM produk p
JOIN transaksi t ON p.id = t.produk_id
WHERE t.total > 200000;
Mistake 3: Lupa Alias di Derived Table
-- SALAH
SELECT * FROM (
SELECT kategori, SUM(total) FROM transaksi GROUP BY kategori
);
-- BENAR
SELECT * FROM (
SELECT kategori, SUM(total) AS total
FROM transaksi
GROUP BY kategori
) AS sub; -- Wajib pake alias
Subquery vs CTE (Common Table Expression)
Buat query yang kompleks, kadang lebih baik pake CTE daripada nested subquery.
-- Pake Subquery (susah dibaca)
SELECT *
FROM (
SELECT kategori, SUM(total) AS total
FROM (
SELECT p.kategori, t.total
FROM produk p
JOIN transaksi t ON p.id = t.produk_id
) AS sub1
GROUP BY kategori
) AS sub2
WHERE total > 300000;
-- Pake CTE (lebih readable)
WITH sales_data AS (
SELECT p.kategori, t.total
FROM produk p
JOIN transaksi t ON p.id = t.produk_id
),
kategori_total AS (
SELECT kategori, SUM(total) AS total
FROM sales_data
GROUP BY kategori
)
SELECT * FROM kategori_total WHERE total > 300000;
CTE bikin query lebih modular dan gampang di-debug.
Tips Praktis
1. Test Subquery-nya Dulu
Sebelum jalanin query lengkap, test subquery-nya terpisah. Pastiin hasilnya sesuai ekspektasi.
2. Perhatikan Performa
Subquery di SELECT atau correlated subquery bisa lambat karena dijalankan per baris. Kalau datanya banyak, consider pake JOIN atau CTE.
3. Pake Alias yang Jelas
Kasih alias yang meaningful biar query gampang dibaca.
4. Comment Query Kompleks
Kalau subquery-nya nested, tambahin comment biar teammate (atau kamu di masa depan) bisa ngerti.
Kesimpulan
Subquery itu tool yang powerful buat Data Analyst. Inget poin-poin utama:
- Subquery di WHERE - buat filter berdasarkan hasil query lain
- Subquery di FROM - bikin tabel sementara (derived table)
- Subquery di SELECT - ngitung nilai per baris
- Correlated subquery - subquery yang reference query luar
- EXISTS vs IN - pilih yang sesuai dengan use case
Mulai dari contoh simple, terus gradually naikin kompleksitasnya. Latihan bikin subquery sendiri pake dataset yang kamu punya.
Happy querying! Cek nguliksql.id buat latihan lebih banyak!
Artikel Terkait
Fungsi GROUP BY dan HAVING di SQL: Panduan Lengkap
Pelajari cara pakai GROUP BY dan HAVING untuk agregasi data di SQL dengan contoh dataset penjualan UMKM Indonesia
Perbedaan INNER JOIN, LEFT JOIN, RIGHT JOIN, dan FULL JOIN di SQL
Belajar perbedaan jenis-jenis JOIN di SQL dengan contoh praktis dan visualisasi. Panduan lengkap INNER, LEFT, RIGHT, dan FULL JOIN untuk pemula.
Cara Install PostgreSQL di Windows, Mac, dan Linux (Panduan Lengkap)
Panduan step-by-step install PostgreSQL di Windows, macOS, dan Linux lengkap dengan pgAdmin dan troubleshooting