Beberapa pertanyaan membutuhkan hasil query lain sebagai bahan: “produk mana yang harganya di atas rata-rata?” menuntut perhitungan rata-rata lebih dulu, baru perbandingan tiap produk terhadap angka tersebut. Subquery — query di dalam query lain — menjawab pola berlapis tersebut dengan rapi. Chapter ini memakai database toko online dari chapter Pengenalan SQL dan ditutup panduan memilih antara subquery dan JOIN.
Subquery Skalar #
Subquery skalar mengembalikan tepat satu baris satu kolom sehingga hasilnya diperlakukan seperti satu nilai biasa. Query dalam kurung dijalankan lebih dahulu, lalu nilainya menggantikan posisi subquery:
SELECT AVG(harga) FROM produkOutput yang Dihasilkan:
(3046666.6666666665,)Angka tersebut kini dipakai sebagai pembanding langsung di klausa WHERE:
SELECT nama, harga
FROM produk
WHERE harga > (SELECT AVG(harga) FROM produk)Output yang Dihasilkan:
('Smartphone X1', 3500000)
('Laptop Pro 14', 12500000)Mari kita telaah: subquery (SELECT AVG(harga) FROM produk) menghasilkan 3.046.666,67; kondisi harga > 3046666.67 lalu menyaring produk satu per satu dan menyisakan dua produk termahal. Tanpa subquery, kita menjalankan dua query terpisah lalu menyalin angkanya secara manual — lambat dan rawan kesalahan saat data berubah.
Subquery pada WHERE dengan IN dan NOT IN #
Subquery juga boleh mengembalikan satu kolom banyak baris, lalu diuji dengan IN. Query berikut mencari pelanggan yang pernah menyelesaikan pesanan:
SELECT nama
FROM pelanggan
WHERE id IN (SELECT pelanggan_id FROM pesanan WHERE status = 'selesai')Output yang Dihasilkan:
('Budi Santoso',)
('Siti Rahayu',)
('Dewi Lestari',)
('Andi Wijaya',)Kebalikannya, NOT IN menemukan pelanggan yang belum pernah menyelesaikan pesanan apa pun:
SELECT nama
FROM pelanggan
WHERE id NOT IN (SELECT pelanggan_id FROM pesanan WHERE status = 'selesai')Output yang Dihasilkan:
('Rina Kartika',)
('Farhan Pratama',)Subquery pada FROM sebagai Derived Table #
Subquery pada klausa FROM — disebut derived table — menghasilkan tabel sementara yang bisa diperlakukan seperti tabel biasa. Pola ini memungkinkan kita memfilter hasil agregasi tanpa HAVING:
SELECT pesanan_id, total
FROM (SELECT pesanan_id, SUM(subtotal) AS total
FROM detail_pesanan
GROUP BY pesanan_id) AS ringkasan
WHERE total > 1000000
ORDER BY total DESCOutput yang Dihasilkan:
(3, 12500000)
(1, 4000000)
(6, 1500000)
(4, 1150000)
(2, 1130000)Subquery di dalam kurung merangkum total belanja per pesanan menjadi tabel bernama ringkasan; query luar tinggal menyaring kolom total dengan WHERE biasa. Setiap derived table wajib diberi alias — tanpa AS ringkasan, database menolak query. Perbedaannya dengan HAVING: keduanya sah, tetapi derived table membuat hasil agregasi bisa langsung dipakai ulang oleh query luar.
Correlated Subquery #
Subquery yang sudah kita lihat berdiri sendiri dan bisa dijalankan terpisah. Subquery berkorelasi (correlated subquery) merujuk kolom dari query luar sehingga dievaluasi ulang untuk tiap baris kandidat — contohnya produk yang harganya di atas rata-rata kategorinya sendiri:
SELECT nama, kategori, harga
FROM produk p
WHERE harga > (SELECT AVG(harga) FROM produk WHERE kategori = p.kategori)Output yang Dihasilkan:
('Laptop Pro 14', 'elektronik', 12500000)
('Sneakers Klasik', 'fashion', 900000)Alias p dari query luar dipanggil di dalam subquery melalui kategori = p.kategori: untuk setiap produk, subquery menghitung rata-rata harga kategorinya (elektronik 5.583.333; fashion 510.000), lalu harga produk dibandingkan dengan angka tersebut. Hanya Laptop Pro 14 dan Sneakers Klasik yang menembus rata-rata kelompok masing-masing.
Menguji Keberadaan dengan EXISTS #
Operator EXISTS menguji apakah subquery mengembalikan setidaknya satu baris — database berhenti memeriksa begitu kecocokan pertama ditemukan, sehingga pola ini efisien untuk pertanyaan keberadaan:
SELECT nama
FROM pelanggan p
WHERE EXISTS (SELECT 1
FROM pesanan ps
WHERE ps.pelanggan_id = p.id
AND ps.status = 'selesai')Output yang Dihasilkan:
('Budi Santoso',)
('Siti Rahayu',)
('Dewi Lestari',)
('Andi Wijaya',)Subquery berkorelasi pada EXISTS memeriksa pesanan selesai untuk tiap pelanggan; SELECT 1 adalah konvensi bahwa isi baris tidak penting — yang diuji hanya ada atau tidaknya baris. Hasilnya sama dengan query IN sebelumnya; optimizer modern kerap menjalankan keduanya dengan rencana eksekusi serupa. EXISTS lebih cocok saat kondisi pencocokan kompleks atau saat butuh negasi yang aman terhadap NULL (NOT EXISTS).
Subquery atau JOIN #
| Aspek | Subquery | JOIN |
|---|---|---|
| Bentuk paling alami untuk | keanggotaan, keberadaan, pembanding agregat | menggabungkan kolom dari beberapa tabel |
| Keterbacaan | menyatakan “apa yang dicari” berlapis | menyatakan relasi antar tabel |
| Kinerja | optimizer modern kerap menyamakan | cenderung mudah dioptimasi |
| Risiko | NOT IN dengan NULL | fan-out duplikasi baris |
Panduan praktisnya: butuh kolom dari tabel lain, pakai JOIN; butuh jawaban sebagai kondisi — daftar, keberadaan, atau angka pembanding — subquery biasanya lebih jelas dibaca. Kedua bentuk boleh dikombinasikan, dan keraguan kinerja dijawab dengan EXPLAIN, bukan tebakan. Rujukan resmi: PostgreSQL
dan SQLite
.
Query berlapis yang panjang cepat membuat sulit membaca alur logika. Chapter Common Table Expressions memperkenalkan klausa WITH untuk memberi nama pada subquery sehingga query analitik yang kompleks tetap rapi.