
Foto oleh panumas nikhomkhai via Pexels
Subquery, atau query di dalam query, adalah salah satu teknik paling fleksibel di SQL Server untuk menjawab pertanyaan yang tidak bisa dijawab hanya dengan satu SELECT sederhana. Subquery memungkinkan hasil dari satu query dipakai sebagai input bagi query lain, baik sebagai filter, sebagai sumber data, maupun sebagai nilai tunggal yang dibandingkan.
Ada tiga posisi utama tempat subquery biasa dipakai: di dalam klausa WHERE untuk memfilter baris berdasarkan hasil query lain, di dalam klausa FROM sebagai tabel turunan (derived table), dan di dalam SELECT sebagai scalar subquery yang menghasilkan satu nilai per baris. Memahami ketiga pola ini membuat developer bisa memilih pendekatan yang paling pas untuk kebutuhan tertentu.
Meskipun kuat, subquery juga punya jebakan performa jika dipakai sembarangan, terutama saat ditulis sebagai correlated subquery yang dieksekusi berulang kali untuk setiap baris pada query luar. Artikel ini akan membahas pola-pola aman penggunaan subquery beserta cara mengenali kapan sebaiknya diganti dengan JOIN atau CTE.
Subquery di WHERE untuk Memfilter Baris
Subquery sebagai filter IN
Scalar Subquery di Dalam SELECT
Scalar subquery menghasilkan satu nilai per baris
Correlated Subquery dan Dampaknya pada Performa
Correlated subquery dengan EXISTS
Subquery sebagai Derived Table di FROM
Derived table memakai hasil agregat sebagai sumber filter
Subquery Berkorelasi vs Non-Korelasi dan Dampaknya pada Performa
Perbedaan eksekusi subquery non-korelasi dan subquery berkorelasi
Subquery non-korelasi hanya bergantung pada dirinya sendiri dan tidak memiliki referensi ke tabel pada query luar, sehingga SQL Server cukup mengeksekusinya satu kali dan menyimpan hasilnya sebagai nilai konstan yang dipakai ulang. Sebaliknya, subquery berkorelasi memiliki klausa WHERE yang merujuk pada kolom dari query luar, sehingga secara konseptual harus dievaluasi ulang untuk setiap baris yang diproses oleh query luar tersebut.
Dampak performa dari subquery berkorelasi menjadi signifikan ketika tabel luar memiliki jumlah baris yang besar, karena pola eksekusi semacam itu mirip dengan nested loop di mana subquery dijalankan berulang kali. Query optimizer SQL Server kadang mampu mentransformasi subquery berkorelasi menjadi join atau menggunakan apply operator yang lebih efisien, namun transformasi ini tidak selalu terjadi, terutama pada query dengan logika agregasi bersyarat yang kompleks.
Sebagai alternatif, banyak subquery berkorelasi yang melakukan agregasi per grup dapat ditulis ulang menggunakan JOIN terhadap hasil GROUP BY, atau memakai window function seperti AVG() OVER (PARTITION BY CustomerID), yang pada contoh di atas akan menghasilkan rencana eksekusi dengan satu kali pemindaian data ketimbang pemanggilan subquery berulang.
Perbedaan EXISTS dan IN pada Subquery serta Dampaknya
IN menghasilkan daftar nilai, EXISTS hanya menguji keberadaan baris
IN dan EXISTS sering dianggap setara karena pada banyak kasus optimizer SQL Server memang mentransformasi keduanya menjadi rencana eksekusi yang identik, biasanya berupa semi join. Namun secara konseptual IN membangun atau membandingkan terhadap kumpulan nilai hasil subquery, sedangkan EXISTS hanya memeriksa apakah subquery mengembalikan minimal satu baris, tanpa peduli nilai kolom yang dikembalikan subquery tersebut.
Perbedaan paling signifikan muncul ketika daftar hasil subquery pada IN mengandung NULL. Jika subquery pada klausa WHERE CustomerID IN (...) mengembalikan NULL di antara nilainya, dan kolom CustomerID pada baris luar tidak cocok dengan nilai mana pun, hasil perbandingan menjadi UNKNOWN yang dapat membuat seluruh kondisi NOT IN gagal mengembalikan baris yang seharusnya muncul. EXISTS tidak memiliki masalah ini karena ia hanya mengevaluasi keberadaan baris, bukan membandingkan nilai satu per satu.
Dari sisi performa, EXISTS umumnya lebih aman dipakai pada subquery berkorelasi karena optimizer bisa langsung berhenti mencari begitu satu baris cocok ditemukan, sementara NOT IN dengan subquery yang berpotensi mengandung NULL sebaiknya dihindari dan diganti NOT EXISTS, karena NOT IN dengan NULL di dalam daftar dapat membuat query secara diam-diam tidak mengembalikan baris apa pun tanpa pesan error. Kebiasaan menambahkan kondisi IS NOT NULL secara eksplisit pada kolom subquery sebelum memakai NOT IN juga menjadi langkah pencegahan yang umum disarankan ketika EXISTS tidak memungkinkan dipakai pada struktur query tertentu.
Scalar Subquery pada SELECT List untuk Menyisipkan Nilai Agregat
Scalar subquery pada SELECT list, dieksekusi per baris mirip subquery berkorelasi
Scalar subquery yang ditulis langsung di dalam SELECT list berfungsi menyisipkan satu nilai tunggal ke setiap baris hasil, berbeda dari subquery pada klausa WHERE yang dipakai untuk menyaring baris. Subquery semacam ini wajib mengembalikan paling banyak satu baris dan satu kolom, karena SQL Server akan menghentikan eksekusi dengan error jika subquery tersebut mengembalikan lebih dari satu nilai pada baris mana pun.
Karena subquery pada SELECT list umumnya berkorelasi dengan kolom dari tabel luar, pola eksekusinya serupa dengan subquery berkorelasi pada WHERE, yaitu berpotensi dievaluasi ulang untuk setiap baris yang diproses. Pada tabel dengan jumlah baris besar, pola ini dapat ditulis ulang memakai window function seperti AVG() OVER (PARTITION BY CategoryID) agar perhitungan agregat cukup dilakukan dalam satu kali pemindaian data. Rekomendasi umum adalah membandingkan execution plan kedua bentuk query tersebut pada volume data produksi sebelum memutuskan mana yang dipertahankan dalam kode aplikasi jangka panjang.
Kesalahan Umum yang Sering Terjadi
Memakai Correlated Subquery untuk Dataset Besar
Correlated subquery yang dieksekusi ribuan kali untuk dataset besar bisa membuat query menjadi sangat lambat dibanding mengubahnya menjadi JOIN atau menggunakan window function, terutama ketika tabel luar punya jutaan baris.
Lupa Scalar Subquery Hanya Boleh Satu Nilai
Menulis scalar subquery yang berpotensi mengembalikan lebih dari satu baris akan menyebabkan error runtime yang baru terlihat saat data bertambah, bukan saat query pertama kali ditulis dan diuji dengan data kecil.
Menggunakan IN dengan Subquery yang Bisa Menghasilkan NULL
Jika subquery di dalam NOT IN menghasilkan nilai NULL pada salah satu barisnya, seluruh hasil NOT IN bisa menjadi kosong tanpa pesan error apa pun, sebuah perilaku yang sering membingungkan developer pemula.
Kapan Pendekatan Ini Tidak Cocok Dipakai
Subquery sangat baik untuk query yang sifatnya sekali pakai atau logikanya cukup sederhana untuk dipahami dalam satu blok, namun untuk query yang kompleks dengan banyak langkah, CTE biasanya lebih mudah dibaca dan dipelihara karena setiap langkah bisa diberi nama yang jelas.
Dari sisi performa, query optimizer SQL Server modern umumnya cukup pintar mengubah subquery menjadi bentuk JOIN secara internal, sehingga perbedaan performa antara subquery dan JOIN untuk kasus non-correlated seringkali tidak signifikan — pengecualian utamanya ada pada correlated subquery yang memang secara desain dieksekusi berulang.
Ringkasan
Subquery adalah alat yang fleksibel untuk memecah masalah kompleks menjadi query-query kecil yang saling bersarang, baik untuk memfilter, membandingkan, maupun menyediakan data turunan. Pilih IN atau EXISTS berdasarkan apakah subquery berkorelasi dengan query luar, dan pertimbangkan CTE jika logika subquery mulai terlalu panjang untuk dibaca dalam satu blok.
Seri Belajar SQL Server:
← Sebelumnya: Menggabungkan Query dengan UNION dan UNION ALL
→ Selanjutnya: Common Table Expression (CTE) di SQL Server (segera terbit)