Pernah gak kamu lagi belanja online, terus kepikiran: "Produk apa sih yang paling laris dari semua kategori yang tersedia?" — Untuk menjawab pertanyaan kayak gitu, kamu butuh dua langkah: pertama cari daftar kategori, baru cari produk terlaris di tiap kategori. Di dunia MySQL, proses dua-langkah inilah yang diselesaikan oleh subquery MySQL nested SELECT — sebuah query yang berjalan di dalam query lain.
Selamat datang di Artikel ke-23 dari 28 dalam seri Belajar Query SQL MySQL! Di artikel ini, kita bakal kupas tuntas teknik subquery MySQL nested SELECT — mulai dari konsep dasarnya, cara kerjanya di klausa WHERE, FROM, dan SELECT, hingga perbandingannya dengan JOIN. Kalau kamu sudah melewati JOIN di artikel-artikel sebelumnya, subquery ini akan terasa seperti senjata baru yang bikin SQL-mu jauh lebih ekspresif.
Semua contoh menggunakan studi kasus toko online dengan tabel produk, pelanggan, dan pesanan — biar langsung relate dan bisa kamu praktikkan.
Menurut survei Stack Overflow Developer Survey, subquery adalah salah satu fitur SQL yang paling sering "takut dipakai" oleh developer junior — padahal lebih dari 70% query analitik di aplikasi e-commerce nyata menggunakan subquery dalam satu bentuk atau lainnya. Setelah artikel ini, kamu nggak akan takut lagi!
1. Apa Itu Subquery? Analogi & Konsep Dasar
Bayangkan kamu minta tolong teman: "Beliin aku minum yang sama kayak yang dibeli si Budi kemarin." Sebelum bisa beliin, temanmu harus tanya dulu ke Budi beli apa — itu adalah query dalam query. Di MySQL, konsepnya persis sama: subquery adalah SELECT yang dieksekusi lebih dulu, hasilnya dipakai oleh query luar (outer query).
Subquery selalu ditulis di dalam tanda kurung ( ) dan bisa muncul di klausa WHERE, FROM, maupun SELECT.
-- Struktur umum subquery di WHERE SELECT kolom1, kolom2 FROM tabel_luar WHERE kolom_x operator ( -- Ini adalah subquery (inner query) SELECT kolom_y FROM tabel_dalam WHERE kondisi_tertentu ); -- Operator bisa berupa: =, >, <, IN, NOT IN, EXISTS, ANY, ALL
๐ Contoh: Tampilkan produk dengan harga di atas rata-rata
-- Tampilkan produk yang harganya di atas rata-rata semua produk SELECT nama_produk, harga, kategori FROM produk WHERE harga > ( SELECT AVG(harga) -- subquery: hitung rata-rata harga FROM produk );
| nama_produk | harga | kategori |
|---|---|---|
| Laptop ProMax 15 | Rp 12.500.000 | Elektronik |
| Smartphone UltraX | Rp 8.750.000 | Elektronik |
| Kamera Mirrorless Z | Rp 9.200.000 | Elektronik |
* AVG(harga) = Rp 5.300.000 — hanya produk di atas nilai ini yang muncul.
Subquery di klausa WHERE yang mengembalikan satu nilai (scalar subquery) paling aman untuk pemula. Pastikan inner query tidak mengembalikan lebih dari satu baris jika kamu menggunakan operator =.
2. Subquery dengan IN dan NOT IN — Filter Berdasarkan Daftar Dinamis
Kalau = hanya cocok untuk subquery yang mengembalikan satu nilai, operator IN adalah sahabatmu ketika subquery menghasilkan banyak baris. Analoginya: kamu punya daftar nama teman yang sudah RSVP ke acaramu, lalu kamu mau cari data lengkap mereka di buku kontak — itulah IN.
๐ Contoh: Tampilkan pelanggan yang pernah memesan produk kategori "Elektronik"
-- Cari ID produk Elektronik dulu, lalu cari pelanggan yang memesannya SELECT nama, email FROM pelanggan WHERE id_pelanggan IN ( SELECT DISTINCT id_pelanggan -- subquery: ambil ID pelanggan FROM pesanan WHERE id_produk IN ( SELECT id_produk -- nested subquery: ID produk Elektronik FROM produk WHERE kategori = 'Elektronik' ) );
| nama | |
|---|---|
| Andi Saputra | andi@example.com |
| Rina Dewi | rina@example.com |
| Budi Santoso | budi@example.com |
๐ Contoh NOT IN: Pelanggan yang BELUM pernah pesan sama sekali
-- Pelanggan yang tidak ada di tabel pesanan sama sekali SELECT nama, email FROM pelanggan WHERE id_pelanggan NOT IN ( SELECT DISTINCT id_pelanggan FROM pesanan WHERE id_pelanggan IS NOT NULL -- PENTING: handle NULL! );
Saat menggunakan NOT IN, kalau subquery-nya mengembalikan satu saja nilai NULL, keseluruhan query akan mengembalikan result kosong! Selalu tambahkan WHERE kolom IS NOT NULL di dalam subquery-mu.
3. Subquery di Klausa FROM — Derived Table (Tabel Sementara)
Nah, ini yang agak bikin pusing tapi super powerful. Subquery bisa dipakai sebagai sumber data di klausa FROM — seolah-olah subquery itu adalah tabel sungguhan. Ini disebut derived table. Analoginya: kamu bikin catatan coretan dulu di kertas baru, lalu kertas itu kamu jadikan sumber data untuk laporan resmi.
Derived table wajib diberi alias — MySQL tidak akan mengizinkan tanpanya.
๐ Contoh: Tampilkan pelanggan dengan total belanja di atas rata-rata
-- Langkah 1 (inner): Hitung total belanja per pelanggan -- Langkah 2 (outer): Filter yang di atas rata-rata SELECT p.nama, ringkasan.total_belanja FROM pelanggan p JOIN ( SELECT id_pelanggan, SUM(total_harga) AS total_belanja -- subquery sebagai tabel FROM pesanan GROUP BY id_pelanggan ) AS ringkasan -- alias WAJIB ada! ON p.id_pelanggan = ringkasan.id_pelanggan WHERE ringkasan.total_belanja > ( SELECT AVG(total_harga) FROM pesanan ) ORDER BY ringkasan.total_belanja DESC;
| nama | total_belanja |
|---|---|
| Rina Dewi | Rp 23.450.000 |
| Budi Santoso | Rp 18.200.000 |
| Andi Saputra | Rp 15.900.000 |
Derived table di klausa FROM adalah alternatif elegan dari CTE (WITH ... AS). Untuk query yang pendek, derived table lebih ringkas. Untuk query yang panjang dan perlu dibaca ulang, gunakan CTE agar lebih mudah di-debug.
4. Subquery di Klausa SELECT — Kolom yang Dihitung Dinamis
Selain WHERE dan FROM, subquery juga bisa masuk langsung ke daftar kolom di SELECT. Hasilnya berupa kolom kalkulasi yang dihitung per baris. Ini disebut correlated subquery karena inner query mengacu pada nilai dari outer query (berjalan per baris).
๐ Contoh: Tampilkan setiap produk beserta jumlah kali dipesan
-- Correlated subquery: inner query mengacu ke p.id_produk (outer) SELECT nama_produk, harga, ( SELECT COUNT(*) FROM pesanan ps WHERE ps.id_produk = p.id_produk -- korelasi ke outer query ) AS jumlah_pesanan FROM produk p ORDER BY jumlah_pesanan DESC;
| nama_produk | harga | jumlah_pesanan |
|---|---|---|
| Smartphone UltraX | Rp 8.750.000 | 47 |
| Headphone BassMax | Rp 450.000 | 38 |
| Laptop ProMax 15 | Rp 12.500.000 | 22 |
| Charger Turbo 65W | Rp 175.000 | 15 |
Correlated subquery di klausa SELECT dieksekusi satu kali per baris dari outer query. Jika tabel produk punya 10.000 baris, subquery dijalankan 10.000 kali! Untuk dataset besar, lebih efisien menggunakan LEFT JOIN + GROUP BY atau derived table.
๐งช Panduan Praktikum: Subquery dari Nol
Siapkan Tabel & Data Contoh
CREATE TABLE produk ( id_produk INT PRIMARY KEY AUTO_INCREMENT, nama_produk VARCHAR(100), harga DECIMAL(15,2), kategori VARCHAR(50) ); CREATE TABLE pelanggan ( id_pelanggan INT PRIMARY KEY AUTO_INCREMENT, nama VARCHAR(100), email VARCHAR(100) ); CREATE TABLE pesanan ( id_pesanan INT PRIMARY KEY AUTO_INCREMENT, id_pelanggan INT, id_produk INT, total_harga DECIMAL(15,2), tanggal DATE );
Mulai dengan Subquery Sederhana (Scalar)
Coba cari produk termahal menggunakan subquery:
SELECT nama_produk, harga FROM produk WHERE harga = (SELECT MAX(harga) FROM produk);
Coba Subquery dengan IN
Tampilkan produk yang pernah ada dalam pesanan:
SELECT nama_produk, harga
FROM produk
WHERE id_produk IN (
SELECT DISTINCT id_produk
FROM pesanan
WHERE id_produk IS NOT NULL
);
Tantangan: Buat Nested Subquery 2 Level
Cari nama pelanggan yang memesan produk kategori "Elektronik" dengan harga di atas 5 juta:
SELECT nama FROM pelanggan
WHERE id_pelanggan IN (
SELECT DISTINCT id_pelanggan FROM pesanan
WHERE id_produk IN (
SELECT id_produk FROM produk
WHERE kategori = 'Elektronik'
AND harga > 5000000
)
);
๐ Perbandingan: Subquery vs JOIN
Pertanyaan klasik: kapan pakai subquery, kapan pakai JOIN? Ini perbandingan singkatnya:
| Aspek | Subquery | JOIN |
|---|---|---|
| Keterbacaan | ✅ Lebih mudah dibaca untuk logika bertingkat | ⚡ Lebih ringkas untuk relasi sederhana |
| Performa | ⚠️ Correlated subquery bisa lambat di data besar | ✅ Umumnya lebih cepat dengan index yang tepat |
| Fleksibilitas | ✅ Bisa di WHERE, FROM, SELECT sekaligus | ⚡ Hanya di FROM/ON clause |
| Agregasi bertingkat | ✅ Ideal untuk "max dari max" atau "avg dari sum" | ⚠️ Butuh derived table atau CTE |
| Duplikasi baris | ✅ Tidak menghasilkan duplikat | ⚠️ Bisa menghasilkan duplikat jika tidak pakai DISTINCT |
| Kapan digunakan? | Filter dinamis, agregasi bertingkat, derived table | Gabung data dari dua tabel atau lebih secara langsung |
Saat subquery-mu tidak menghasilkan output yang diharapkan, jalankan inner query terlebih dahulu secara terpisah. Copy bagian dalam kurung, eksekusi sendiri, dan periksa hasilnya. Ini cara tercepat untuk isolasi bug tanpa perlu menebak-nebak.
Ketika kamu hanya perlu mengecek apakah ada data yang cocok (bukan mengambil nilainya), gunakan EXISTS — ini lebih efisien dari IN pada data besar karena MySQL berhenti mencari begitu satu baris ditemukan.
-- EXISTS: lebih cepat untuk "apakah ada pesanan?"
SELECT nama FROM pelanggan p
WHERE EXISTS (
SELECT 1 FROM pesanan ps
WHERE ps.id_pelanggan = p.id_pelanggan
);
Artikel Berikutnya dalam Seri
Artikel 24: Common Table Expressions (CTE) dengan WITH
Subquery sudah kamu kuasai — sekarang saatnya naik level ke CTE! Pelajari cara menulis query kompleks yang jauh lebih mudah dibaca dan di-debug menggunakan klausa WITH di MySQL 8+.
Baca Artikel 24 →
No comments:
Post a Comment