TL;DR
Code review SQL adalah pemeriksaan query oleh orang lain sebelum query itu dipakai buat laporan atau masuk ke pipeline. Lima hal yang dicek berurutan: kebenaran hasil, duplikasi baris dari JOIN, penanganan NULL, performa, dan kebacaan. Urutannya penting karena query yang rapi tapi angkanya salah jauh lebih berbahaya daripada query berantakan yang benar.
Code review SQL adalah pemeriksaan query oleh orang lain sebelum query itu dipakai buat laporan atau masuk ke pipeline.
Yang dicek bukan cuma rapi atau nggaknya. Urutan pemeriksaannya begini: kebenaran hasil dulu, baru duplikasi baris, penanganan NULL, performa, dan terakhir kebacaan.
Urutan itu penting. Query rapi yang angkanya salah jauh lebih berbahaya daripada query berantakan yang benar, soalnya yang rapi lebih dipercaya orang.
Kebenaran hasil. Reviewer ngecek apakah query-nya beneran jawab pertanyaan yang diminta, bukan pertanyaan mirip. Cara paling cepat: bandingin satu angka hasil query sama angka dari sumber lain yang udah dipercaya, misalnya laporan bulanan atau dasbor yang udah jalan.
Empat pertanyaan yang aku ajukan ke diri sendiri sebelum baca detail sintaksnya:
Nomor 4 yang paling sering jadi sumber ribut. "Pelanggan aktif" bisa berarti pernah belanja 30 hari terakhir menurut tim marketing dan 90 hari menurut tim keuangan. Query kamu benar secara teknis, tapi jawabannya beda.
Hitung jumlah baris sebelum dan sesudah JOIN. Kalau tabel kiri punya 3.104 baris dan hasil JOIN jadi 3.418, berarti ada baris yang kepenggandaan. Penyebab paling umum: tabel kanan punya lebih dari satu baris per kunci, misalnya satu pelanggan punya dua alamat.
-- Cek duplikasi kunci di tabel kanan sebelum JOIN
SELECT pelanggan_id, COUNT(*)
FROM alamat
GROUP BY pelanggan_id
HAVING COUNT(*) > 1;
Kalau query di atas ngasih hasil, JOIN kamu bakal gandain baris. Solusinya ambil satu baris per kunci dulu di CTE terpisah, baru di-JOIN.
Jebakan kedua yang sering lolos review: kondisi tabel kanan ditaruh di WHERE pada LEFT JOIN.
| Yang ditulis | Yang terjadi |
|---|---|
LEFT JOIN t ... WHERE t.status = 'selesai' | Berubah jadi INNER JOIN, pelanggan tanpa transaksi hilang |
LEFT JOIN t ... AND t.status = 'selesai' | Tetap LEFT JOIN, pelanggan tanpa transaksi tetap muncul dengan NULL |
Bedanya satu kata, hasilnya beda ratusan baris. Kesalahan sejenis ini aku kumpulin di daftar kesalahan umum SQL pemula.
Tiga titik: agregat yang ngelewatin NULL, pembagian yang bisa kena nol, dan perbandingan pakai tanda sama dengan. NULL nggak sama dengan apa pun, termasuk NULL lain, jadi WHERE kolom = NULL selalu ngasih nol baris.
| Situasi | Risiko | Perbaikan |
|---|---|---|
AVG(nilai) dengan NULL | Penyebutnya cuma baris terisi | AVG(COALESCE(nilai, 0)) kalau NULL berarti nol |
a / b | Error kalau b nol | a / NULLIF(b, 0) |
WHERE kolom = NULL | Selalu kosong | WHERE kolom IS NULL |
kolom NOT IN (subquery) | Kosong semua kalau subquery punya NULL | Pakai NOT EXISTS |
SUM dari tabel kosong | Hasil NULL, bukan nol | COALESCE(SUM(x), 0) |
Baris keempat itu yang paling jahat karena diamnya total. NOT IN dengan subquery yang mengandung satu NULL aja bakal ngasih nol baris tanpa error apa pun.
Konteks lebih luasnya ada di halaman data quality.
Reviewer ngecek apakah query bakal tetap jalan waktu datanya sepuluh kali lipat. Tiga hal yang paling sering jadi masalah: fungsi yang dipasang di kolom yang difilter, SELECT bintang di tabel besar, dan subquery yang jalan per baris.
-- Lambat: fungsi di kolom bikin index nggak kepakai
WHERE DATE(tanggal_transaksi) = '2025-09-06'
-- Cepat: rentang, index tetap kepakai
WHERE tanggal_transaksi >= '2025-09-06'
AND tanggal_transaksi < '2025-09-07'
Empat pemeriksaan cepat:
Buat mastiin, jalanin EXPLAIN ANALYZE dan lihat mana yang jadi biaya terbesar. Cara bacanya dijelasin di dokumentasi EXPLAIN PostgreSQL.
Query yang susah dibaca bikin review jadi dangkal, dan review dangkal cuma nangkep masalah format. Empat hal yang paling nentuin: penamaan alias, pemakaian CTE ketimbang subquery bertingkat, satu kolom per baris di SELECT, dan komentar yang jelasin alasan.
| Hal | Yang bikin susah | Yang bikin enak |
|---|---|---|
| Alias | a, b, t1 | trx, pel, prd |
| Struktur | Subquery 4 lapis | CTE bernama, dibaca atas ke bawah |
| SELECT | 12 kolom dalam 1 baris | 1 kolom per baris |
| Komentar | "ambil data penjualan" | "buang akun QA, dipakai tim uji coba" |
| Nama kolom hasil | col1, jml | jumlah_transaksi, total_nilai |
Aturan lengkapnya ada di SQL style guide. Satu gaya yang dipakai semua orang lebih berguna daripada gaya sempurna yang cuma dipakai satu orang.
Query di kolom SQL artikel ini datang dari review nyata di dataset toko_berkah punya ngulikdata, 3.104 pelanggan dan 41.892 baris transaksi.
Versi awalnya kelihatan wajar. Tapi hasilnya ngasih 3.418 baris buat 3.104 pelanggan.
Setelah dicek, 219 pelanggan punya lebih dari satu alamat, dan JOIN ke tabel alamat gandain barisnya. Total nilai yang keluar Rp 1.723 juta, padahal angka sebenarnya Rp 1.558 juta.
Selisihnya 10,6 persen. Cukup buat bikin keputusan promo meleset, dan nggak cukup besar buat langsung kelihatan salah.
Masalah kedua: WHERE t.status = 'selesai' bikin 487 pelanggan yang belum pernah transaksi selesai hilang dari hasil, padahal laporannya minta semua pelanggan.
Masalah ketiga: pembagian buat rata-rata nilai bakal error begitu ada pelanggan dengan nol transaksi masuk ke hasil.
Versi perbaikannya nutup ketiganya: alamat diambil satu per pelanggan lewat CTE, filter status dipindah ke dalam CTE transaksi, dan pembagian dibungkus NULLIF.
Cara nyari dan nanganin baris ganda dibahas lebih panjang di panduan deduplikasi data dengan SQL.
| Urutan | Yang dicek | Cara cepat |
|---|---|---|
| 1 | Jawab pertanyaan yang bener | Bandingin 1 angka sama sumber lain |
| 2 | Tingkat rincian dan filter | Cek definisi metrik sama tim peminta |
| 3 | Duplikasi baris dari JOIN | Hitung baris sebelum dan sesudah |
| 4 | Kondisi LEFT JOIN di WHERE | Cari nama tabel kanan di klausa WHERE |
| 5 | NULL di agregat dan pembagian | Cari tanda bagi dan NOT IN |
| 6 | Fungsi di kolom yang difilter | Baca ulang semua WHERE |
| 7 | Alias dan struktur CTE | Bisa dibaca atas ke bawah nggak |
| 8 | Komentar alasan | Tiap filter aneh ada penjelasannya |
Nomor 1 sampai 5 wajib. Nomor 6 sampai 8 saran, dan nggak boleh nahan query yang angkanya udah bener.
Perlu kalau angkanya bakal dipakai buat keputusan atau dikirim ke luar tim. Query sekali pakai punya risiko lebih besar justru karena nggak ada yang ngecek ulang. Buat query eksplorasi yang cuma buat kamu sendiri, review nggak perlu. Patokannya bukan sekali pakai atau nggak, tapi ada yang ambil keputusan dari angkanya atau nggak.
Komentari query-nya, bukan orangnya, dan sertakan alasannya. "Ini salah" bikin orang defensif. "JOIN ke alamat bisa gandain baris kalau ada pelanggan yang punya 2 alamat, coba cek jumlah barisnya sebelum dan sesudah" ngasih arah yang jelas. Pisahin juga mana yang wajib dibenerin dan mana yang cuma saran.
Sekitar 15 sampai 30 menit buat query 50 sampai 100 baris. Kalau lebih dari sejam, biasanya query-nya kepanjangan dan mendingan diminta pecah jadi beberapa CTE dulu. Review yang buru-buru dalam 3 menit hampir selalu cuma nangkep masalah format, bukan masalah logika.
Jalanin, minimal bagian pemeriksaan jumlah baris. Baca doang bikin kamu ketinggalan duplikasi baris yang cuma kelihatan dari angka. Kalau nggak bisa akses datanya, minta penulis query nyertain hasil pemeriksaan: jumlah baris sebelum dan sesudah JOIN, plus perbandingan total sama angka dari sumber lain.
SQLFluff buat cek format dan aturan penulisan, populer di tim yang pakai dbt. Buat performa, EXPLAIN ANALYZE bawaan database kamu udah cukup. Tapi tool cuma nangkep format dan biaya eksekusi. Kebenaran logika, duplikasi baris, dan penanganan NULL tetap harus dicek manusia yang ngerti datanya.
Dua pemeriksaan yang paling sering nyelametin angka: hitung baris sebelum dan sesudah JOIN, lalu cari nama tabel kanan di klausa WHERE.
Dua itu doang udah nutup mayoritas kesalahan yang aku temui.
Ambil query terakhir yang kamu tulis, jalanin dua pemeriksaan itu sekarang. Sepuluh menit, dan kemungkinan besar kamu nemu satu hal yang perlu dibenerin.
Kalau timmu belum punya aturan penulisan yang disepakati, mulai dari SQL style guide.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Kolom waktu isinya angka gede kayak 1704067200 dan bikin bingung? Itu Unix timestamp. Ini cara ngubahnya jadi tanggal beneran di SQL, plus balik lagi.
Data transaksi tersimpan UTC, tapi laporan harus jam WIB. Kalau salah konversi, angka penjualan tengah malam bisa kecatat di tanggal yang salah. Ini cara handle timezone di SQL dengan benar.
Nambah 30 hari ke tanggal invoice, ngurangin sebulan buat cari periode lalu, ngitung selisih hari antar order. Semua itu aritmetika tanggal, dan SQL punya operator INTERVAL buat ngerjainnya.