Topik ini membuka modul subquery dan CTE dengan tiga bentuk subquery yang paling sering dipakai, yaitu subquery skalar yang menghasilkan satu nilai, subquery daftar yang dipakai bersama IN, dan EXISTS yang menguji keberadaan. Ketiganya menempatkan query di dalam query, dan bentuk masing-masing melayani pertanyaan yang berbeda.
Blok penyiapan dataset#
# siapkan ulang tabel dan data contoh untuk topik ini
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS peminjaman, buku, anggota, kategori CASCADE;
CREATE TABLE kategori (id SERIAL PRIMARY KEY, nama TEXT NOT NULL UNIQUE);
CREATE TABLE anggota (id SERIAL PRIMARY KEY, nama TEXT NOT NULL, email TEXT UNIQUE, bergabung DATE NOT NULL, aktif BOOLEAN NOT NULL DEFAULT TRUE);
CREATE TABLE buku (id SERIAL PRIMARY KEY, judul TEXT NOT NULL, penulis TEXT NOT NULL, tahun INTEGER NOT NULL, kategori_id INTEGER REFERENCES kategori(id));
CREATE TABLE peminjaman (id SERIAL PRIMARY KEY, anggota_id INTEGER NOT NULL REFERENCES anggota(id), buku_id INTEGER NOT NULL REFERENCES buku(id), tanggal_pinjam DATE NOT NULL, tanggal_kembali DATE);
INSERT INTO kategori (nama) VALUES ('Pemrograman'), ('Sains'), ('Sejarah'), ('Fiksi'), ('Statistika');
INSERT INTO anggota (nama, email, bergabung, aktif) VALUES ('Ali Rahman', '[email protected]', '2024-01-15', TRUE), ('Budi Santoso', '[email protected]', '2024-03-02', TRUE), ('Citra Kirana', '[email protected]', '2024-05-20', FALSE), ('Dewi Lestari', '[email protected]', '2024-08-11', TRUE), ('Eka Pratama', NULL, '2025-01-30', TRUE);
INSERT INTO buku (judul, penulis, tahun, kategori_id) VALUES ('Dasar Basis Data', 'Sari Wulandari', 2021, 1), ('Jaringan Komputer', 'Ahmad Fauzi', 2019, 1), ('Fisika untuk Pemula', 'Rina Marlina', 2020, 2), ('Kimia Organik', 'Rina Marlina', 2022, 2), ('Sejarah Nusantara', 'Bambang Wijaya', 2018, 3), ('Cerita Kota Lama', 'Nur Aisyah', 2023, 4), ('Statistika Terapan', 'Sari Wulandari', 2022, 5), ('Algoritma dan Struktur Data', 'Ahmad Fauzi', 2021, 1);
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, tanggal_kembali) VALUES (1, 1, '2025-03-01', '2025-03-15'), (1, 7, '2025-03-05', '2025-03-20'), (2, 3, '2025-03-10', NULL), (3, 5, '2025-02-20', '2025-03-01'), (2, 1, '2025-04-02', NULL), (4, 6, '2025-04-07', NULL), (5, 8, '2025-04-09', '2025-04-25'), (4, 2, '2025-05-01', NULL);
SQLSubquery skalar#
Subquery skalar menghasilkan tepat satu baris satu kolom, sehingga dipakai di tempat ekspresi berada, baik pada daftar SELECT maupun perbandingan WHERE. Pertanyaan buku terbaru dijawab dengan membandingkan tahun terbit terhadap maksimum yang dihitung server.
# buku dengan tahun terbit paling baru
psql -U postgres -d perpustakaan -c "SELECT judul, tahun FROM buku WHERE tahun = (SELECT MAX(tahun) FROM buku);" judul | tahun
------------------+-------
Cerita Kota Lama | 2023
(1 row)Subquery dijalankan terlebih dahulu sebagai satu nilai, yaitu 2023, dan perbandingannya menyaring buku yang cocok. Perbedaannya dengan menulis angka 2023 secara manual adalah ketahanan, karena data yang bertambah tidak memerlukan penyuntingan query, dan subquery yang menghasilkan lebih dari satu baris ditolak server dengan pesan lebih dari satu baris.
IN dan NOT IN#
Subquery yang menghasilkan satu kolom berisi banyak baris dipakai bersama IN, dan maknanya keanggotaan pada himpunan hasil. Bentuk ini paling alami untuk menyaring baris yang rujukannya ada di daftar yang dihitung.
# anggota yang pernah meminjam buku kategori Pemrograman
psql -U postgres -d perpustakaan -c "SELECT nama FROM anggota WHERE id IN (SELECT p.anggota_id FROM peminjaman p JOIN buku b ON p.buku_id = b.id JOIN kategori k ON b.kategori_id = k.id WHERE k.nama = 'Pemrograman') ORDER BY nama;" nama
--------------
Ali Rahman
Budi Santoso
Dewi Lestari
Eka Pratama
(4 rows)Subquery menghasilkan daftar id anggota dari peminjaman buku pemrograman, dan IN menyaring anggota yang id-nya ada di daftar itu. NOT IN membalik keanggotaannya, tetapi warisan semantik NULL-nya serius sebagaimana dibahas pada topik semantik NULL, yaitu satu NULL pada daftar mengosongkan seluruh hasil, sehingga kolom subquery yang boleh NULL sebaiknya ditangani dengan NOT EXISTS.
EXISTS menguji keberadaan#
EXISTS menanyakan apakah subquery menghasilkan setidaknya satu baris, tanpa peduli isinya, sehingga subquerynya lazim ditulis SELECT 1. Kekuatannya pada penghubung yang merujuk baris luar, yang menjadi pintu masuk subquery berkorelasi pada topik tersendiri.
# kategori yang sudah memiliki buku
psql -U postgres -d perpustakaan -c "SELECT nama FROM kategori k WHERE EXISTS (SELECT 1 FROM buku b WHERE b.kategori_id = k.id) ORDER BY nama;" nama
-------------
Fiksi
Pemrograman
Sains
Sejarah
Statistika
(5 rows)Server berhenti menyapu subquery begitu satu baris ditemukan, sifat yang disebut semi-join, dan bentuk negatifnya NOT EXISTS tidak mewarisi racun NULL milik NOT IN karena keberadaan baris dites sungguhan. Untuk pertanyaan keberadaan, EXISTS menjadi bentuk yang paling aman sekaligus paling hemat.
Kesalahan umum#
Kesalahan pertama adalah subquery skalar yang menghasilkan lebih dari satu baris, yang ditolak server, dan perbaikannya menyaring subquery atau mengubahnya menjadi agregat. Kesalahan kedua adalah NOT IN terhadap subquery dengan kolom yang boleh NULL, yang mengosongkan hasil tanpa error, dan penggantinya NOT EXISTS.
Kesalahan ketiga adalah menyusun subquery yang sebenarnya adalah join biasa, sehingga query menjadi lebih dalam daripada perlu, dan aturan praktisnya memakai subquery saat kebutuhannya nilai atau keberadaan, sedangkan pasangan kolom antartabel tetap wilayah join. Kesalahan keempat adalah merujuk alias luar tanpa sengaja, yang mengubah subquery biasa menjadi berkorelasi dan mengubah biayanya secara diam-diam.
Satu bentuk lagi yang melengkapi tiga bentuk di atas adalah LATERAL, yaitu subquery pada FROM yang boleh merujuk kolom tabel sebelumnya di FROM yang sama, sehingga perhitungan per baris menjadi bagian dari klausa FROM alih-alih daftar SELECT. Bentuk ini melayani kebutuhan seperti mengambil tiga teratas per kelompok dari fungsi yang mengembalikan tabel, wilayah yang dulu hanya dicapai subquery berkorelasi. Dokumentasi tabel ekspresi mencantumkannya sebagai bentuk resmi, dan penulisannya tetap memakai kata LATERAL di depan subquery.
Ringkasan#
- Subquery skalar menghasilkan satu nilai dan dipakai di tempat ekspresi, dengan penolakan tegas bila lebih dari satu baris.
- IN menguji keanggotaan pada hasil subquery, sedangkan NOT IN mewarisi racun NULL dan digantikan NOT EXISTS.
- EXISTS menguji keberadaan baris dan berhenti pada temuan pertama, dengan bentuk negatifnya yang aman terhadap NULL.
- Subquery dipilih untuk kebutuhan nilai dan keberadaan, sedangkan penyandingan kolom antartabel tetap wilayah join.
Referensi#
- Subquery Expressions — PostgreSQL Documentation — Referensi IN, EXISTS, dan bentuk subquery lainnya.
- Scalar Subqueries — PostgreSQL Documentation — Aturan subquery skalar pada ekspresi.
- Queries — PostgreSQL Documentation — Bab resmi tentang penyusunan query.