Subquery adalah query di dalam query lain, ditulis di dalam kurung. Yang di dalam jalan duluan, lalu hasilnya dipakai oleh query luar. Mirip pertanyaan "siapa yang tingginya di atas rata-rata kelas?": kamu harus menghitung rata-ratanya dulu (pertanyaan kecil), baru membandingkan setiap siswa dengan angka itu (pertanyaan besar).
🧪 Tebakan cepat: tabel
productsberisi Laptop 12.000.000, Mouse 150.000, Monitor 3.500.000, Keyboard 500.000 dan Headset 750.000. Berapa produk yang harganya di atas rata-rata? Tebak dulu, jawabannya ada di bagian "Langkah demi langkah".
Kenapa butuh subquery
Cara yang paling terpikir duluan gak jalan:
SELECT name, price FROM products
WHERE price > AVG(price);
-- misuse of aggregate function AVG()
WHERE memeriksa satu baris sekaligus: "harga baris ini lebih dari ...?". Sedangkan AVG(price) butuh semua baris untuk dihitung. Dua hal itu gak bisa dicampur di satu WHERE, makanya SQLite menolak. Solusinya: hitung rata-ratanya di query terpisah, lalu taruh query itu di dalam kurung.
Langkah demi langkah
SELECT name, price
FROM products
WHERE price > (
SELECT AVG(price) FROM products
);
Langkah 1, query dalam. SELECT AVG(price) FROM products menjumlahkan semua harga, 16.900.000, lalu membaginya dengan 5 produk: 3.380.000. Hasilnya satu angka.
Langkah 2, query luar. Kurung tadi diganti angkanya, jadi query-nya sekarang WHERE price > 3380000, dan setiap baris diperiksa:
| name | price | price > 3380000? |
|---|---|---|
| Laptop | 12.000.000 | ya |
| Mouse | 150.000 | tidak |
| Monitor | 3.500.000 | ya |
| Keyboard | 500.000 | tidak |
| Headset | 750.000 | tidak |
Hasilnya 2 produk: Laptop dan Monitor. Itu jawaban tebakannya.
- Subquery selalu di dalam kurung
( ... ). - Subquery yang menghasilkan satu nilai (satu baris, satu kolom) boleh dipakai di mana pun angka biasa boleh: setelah
>,<,=. - Kalau harganya berubah besok, rata-ratanya ikut dihitung ulang. Itu bedanya dengan menulis
3380000langsung.
Subquery yang menghasilkan daftar: IN
Subquery juga bisa menghasilkan satu kolom berisi banyak baris. Pakai dengan IN, artinya "ada di daftar ini". Pakai tabel pelanggan dan pesanan dari pelajaran JOIN (Budi, Siti, Andi, dan pesanan Laptop, Mouse, Monitor, Flashdisk):
SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders);
Query dalam menghasilkan daftar 1, 1, 2, NULL. Query luar menyimpan pelanggan yang id-nya ada di daftar itu: Budi dan Siti, masing-masing sekali, walau Budi punya dua pesanan. Itu kelebihan IN dibanding JOIN kalau kamu cuma butuh "siapa saja", bukan "pesanan apa saja".
📌 Subquery di SELECT
Subquery juga bisa jadi kolom. Yang ini jalan sekali untuk setiap pelanggan, karena dia memakai c.id dari query luar:
SELECT c.name,
(SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS jumlah
FROM customers c;
Budi 2, Siti 1, Andi 0. Subquery yang memakai kolom dari query luar seperti ini disebut correlated subquery.
⚠️ Jebakan yang sering ketemu
- Menulis angkanya langsung.
WHERE price > 3380000memberi hasil yang sama hari ini, tapi salah begitu harga berubah. Latihan memeriksa bahwa rata-ratanya dihitung oleh(SELECT AVG(...) ...). NOT INdenganNULLdi daftar.WHERE id NOT IN (SELECT customer_id FROM orders)memberi 0 baris, padahal Andi belum pernah pesan. Daftarnya berisiNULL(Flashdisk), dan "3 bukanNULL?" jawabannya "gak diketahui", jadi Andi ikut dibuang. TambahkanWHERE customer_id IS NOT NULLdi subquery-nya, atau pakai LEFT JOIN ... IS NULL dari pelajaran sebelumnya.- Subquery banyak baris setelah
=atau>.WHERE id = (SELECT customer_id FROM orders)di SQLite diam-diam memakai baris pertamanya saja. MySQL dan PostgreSQL menolaknya dengan error. Untuk daftar, pakaiIN. - Lupa kurung, atau lupa
SELECTdi dalamnya.WHERE price > (AVG(price) FROM products)bukan subquery. Isi kurungnya harus query lengkap yang diawaliSELECT.
TL;DR: Subquery = query di dalam kurung yang jalan duluan, lalu hasilnya dipakai query luar. WHERE price > (SELECT AVG(price) FROM products) menghitung rata-rata 3.380.000 dulu, lalu menyimpan Laptop dan Monitor. WHERE price > AVG(price) error karena WHERE memeriksa satu baris, sedangkan AVG butuh semua baris. Subquery satu nilai dipakai dengan >, <, =. Subquery daftar dipakai dengan IN. Hati-hati NOT IN kalau daftarnya bisa berisi NULL: hasilnya jadi kosong.