TL;DR
Validasi query SQL sebelum hasilnya dikirim dilakukan lewat sepuluh pemeriksaan cepat: jumlah baris masuk akal, JOIN nggak nggandain baris, tidak ada baris yang hilang karena NULL, batas tanggal sesuai, tidak ada duplikat di kunci, dan total angkanya cocok dengan sumber lain. Query yang jalan tanpa pesan error belum tentu benar, karena kesalahan logika selalu menghasilkan angka yang kelihatan wajar.
Query yang jalan tanpa pesan error belum tentu bener.
Kesalahan logika di SQL hampir nggak pernah kelihatan sebagai error. Dia muncul sebagai angka yang kelihatan wajar, lolos ke slide, dan baru ketahuan pas ada yang nanya kenapa omzetnya beda sama laporan keuangan.
Sepuluh pemeriksaan di bawah ini bisa kamu jalanin dalam 5 menit. Urutannya dari yang paling sering bikin salah.
Karena database cuma ngejalanin apa yang kamu tulis, bukan apa yang kamu maksud. Salah satu tanda kutip di filter status, satu JOIN yang nggandain baris, atau batas tanggal yang kelewat satu hari semuanya menghasilkan angka yang terlihat masuk akal.
Biaya salahnya nggak seimbang. Ngecek makan 5 menit, sementara narik balik angka yang udah dipakai buat keputusan makan waktu berhari-hari plus kredibilitasmu.
| No | Yang dicek | Cara cepat | Tanda bahaya |
|---|---|---|---|
| 1 | Jumlah baris masuk akal | COUNT sebelum dan sesudah | Naik atau turun drastis |
| 2 | JOIN nggandain baris | COUNT dibanding COUNT DISTINCT kunci | Dua angkanya beda |
| 3 | Baris hilang gara-gara INNER JOIN | LEFT JOIN lalu cek IS NULL | Ada baris tanpa pasangan |
| 4 | Batas tanggal | MIN dan MAX tanggal di hasil | Kelebihan atau kurang sehari |
| 5 | Duplikat di kolom kunci | GROUP BY dengan HAVING COUNT lebih dari 1 | Ada baris kembar |
| 6 | NULL kebuang filter | Hitung jumlah NULL per kolom | Kolom filter banyak kosongnya |
| 7 | Filter status | Lihat daftar nilai unik status | Transaksi batal ikut kehitung |
| 8 | Pembagian nol | Bungkus penyebut pakai NULLIF | Error atau hasil tak hingga |
| 9 | Nilai ekstrem | MIN, MAX, AVG per kolom angka | Nominal minus atau kelewat besar |
| 10 | Rekonsiliasi total | Banding sama satu sumber lain | Selisih di atas 1% |
Fan-out JOIN. Ini kejadian waktu satu baris di tabel kiri ketemu banyak baris pasangan di tabel kanan, jadi baris aslinya berlipat dan semua penjumlahan jadi kegedean.
Gejalanya khas: total penjualan tiba-tiba naik dua kali lipat setelah kamu nambah satu JOIN yang "cuma buat ngambil nama kota".
-- Sebelum JOIN
SELECT COUNT(*) FROM transaksi;
-- Sesudah JOIN, dua angka ini harus sama
SELECT COUNT(*) AS baris_hasil,
COUNT(DISTINCT t.transaksi_id) AS transaksi_unik
FROM transaksi t
JOIN alamat a ON a.pelanggan_id = t.pelanggan_id;
Kalau baris_hasil lebih besar dari transaksi_unik, berarti ada pelanggan yang punya lebih dari satu alamat. Perbaikannya bisa lewat pembatasan alamat utama, atau agregasi dulu sebelum digabung.
Perilaku tiap jenis JOIN aku bahas terpisah di perbedaan JOIN di SQL.
Masalah paling umum ada di kolom bertipe timestamp yang difilter pakai BETWEEN. Kolom yang nyimpen jam bikin transaksi pukul 14.30 di tanggal akhir ikut kebuang, karena batas atasnya dibaca sebagai pukul 00.00.
| Cara nulis | Aman? | Catatan |
|---|---|---|
| BETWEEN '2025-08-01' AND '2025-08-31' | Berisiko | Kehilangan transaksi 31 Agustus setelah tengah malam |
| >= '2025-08-01' AND < '2025-09-01' | Aman | Pola setengah terbuka, kepakai buat semua tipe |
| DATE(tanggal) = '2025-08-01' | Berisiko | Bisa bikin index nggak kepakai |
Pola setengah terbuka itu kebiasaan yang paling gampang dipertahankan. Aturan tipe tanggal dan waktu di PostgreSQL bisa kamu cek di dokumentasi resmi PostgreSQL.
Biasanya gara-gara NULL. Baris dengan nilai kosong nggak lolos perbandingan biasa, jadi dia diam-diam kebuang tanpa peringatan.
-- Baris dengan status NULL nggak ikut, walaupun kamu kira ikut
WHERE status <> 'batal'
-- Versi yang ngajak NULL ikut serta
WHERE (status <> 'batal' OR status IS NULL)
Sebelum bikin filter, lihat dulu daftar nilai unik di kolom itu. Sering kali ada nilai yang kamu nggak tahu keberadaannya, misalnya Batal dengan huruf besar dan batal huruf kecil sekaligus.
SELECT status, COUNT(*) AS jumlah
FROM transaksi
GROUP BY status
ORDER BY jumlah DESC;
Perilaku NULL di operasi agregat aku ringkas di glossary aggregate function, dan pemeriksaan kualitas datanya di glossary data quality.
Aku pakai dataset ngulikdata mega_retail, jaringan toko dengan 12 cabang. Permintaannya sederhana: total omzet Agustus 2025 per cabang.
Query pertama ngasih Rp 8.417.900.000. Laporan kasir ngasih Rp 7.982.400.000. Selisihnya Rp 435.500.000, atau 5,5%.
| Pemeriksaan | Temuan | Dampak |
|---|---|---|
| Filter status | 1.284 transaksi berstatus batal ikut kehitung | Rp 391.200.000 |
| Batas tanggal | BETWEEN motong 31 Agustus sore | Kurang Rp 62.800.000 |
| Duplikat | 217 baris kembar dari impor ulang | Rp 107.100.000 |
Setelah tiga hal itu dibenerin, angkanya jadi Rp 7.983.800.000. Sisa selisihnya Rp 1.400.000, atau 0,02%, dan itu dari pembulatan pajak di sisi kasir.
Yang menarik, dua kesalahan itu arahnya berlawanan. Transaksi batal bikin angkanya kegedean, batas tanggal bikin kekecilan. Kalau kamu cuma ngecek total tanpa mecah per penyebab, selisih 5,5% bisa kelihatan seperti satu masalah kecil.
sum bikin penerima nebak isinya.Buat query harian yang polanya udah kamu kenal, 5 menit cukup: cek jumlah baris, cek fan-out, cek batas tanggal. Buat angka yang bakal dipakai buat keputusan besar atau dilihat di luar tim, sisihkan 20-30 menit dan tambahin rekonsiliasi ke satu sumber lain. Patokan praktisnya, makin banyak orang yang bakal lihat angkanya, makin lama waktu yang pantas kamu keluarkan buat ngecek.
Bandingin jumlah baris hasil dengan jumlah nilai unik dari kolom kunci tabel utama. Kalau COUNT bintang lebih besar dari COUNT DISTINCT kunci, berarti ada baris yang berlipat. Cara lain yang lebih cepat, jalanin query tanpa JOIN dulu lalu catat jumlah barisnya, baru tambahin JOIN satu per satu sambil ngecek angkanya tiap kali. Begitu angkanya naik, kamu langsung tahu JOIN mana penyebabnya.
Selisih di bawah 0,1% biasanya datang dari pembulatan atau beda waktu potret data, dan itu wajar asal kamu bisa jelasin sumbernya. Selisih di atas 1% hampir selalu berarti ada beda definisi, misalnya laporan keuangan ngeluarin retur sementara querymu ikut ngitung. Yang penting bukan angkanya sama persis, tapi kamu tahu persis kenapa beda dan bisa nyebutin penyebabnya.
Buat laporan yang jalan rutin, iya. Simpan satu berkas berisi query pemeriksaan standar, lalu jalanin sebelum ngirim. Isinya cukup lima hal: jumlah baris, jumlah duplikat kunci, jumlah NULL per kolom penting, rentang tanggal, serta nilai terkecil dan terbesar kolom angka. Sekali disiapkan, ini cuma makan waktu dua menit tiap dipakai dan nyelametin kamu berkali-kali.
Justru query lambat yang paling perlu divalidasi, karena kamu bakal males ngulang kalau hasilnya salah. Jalanin pemeriksaan di potongan data yang lebih kecil dulu, misalnya satu cabang atau satu minggu, biar cepat. Setelah logikanya kamu yakin bener, baru jalankan di rentang penuh. Cara memperbaiki kecepatannya sendiri aku bahas terpisah di artikel soal query lambat.
Tiga hal yang perlu kamu bawa. Kesalahan SQL muncul sebagai angka wajar, bukan sebagai error. Fan-out JOIN dan filter status penyebab paling sering. Dan rekonsiliasi ke satu sumber lain wajib buat angka yang bakal dipakai ngambil keputusan.
Simpan sepuluh pemeriksaan itu jadi satu berkas SQL di folder kerjamu hari ini, biar besok tinggal jalanin.
Buat query pemeriksaan kualitas data yang lebih lengkap, baca cek kualitas data pakai SQL. Kalau kamu sering kejebak jebakan sintaks, cek kesalahan umum SQL pemula. Dan kalau querymu lambat, mulai dari cara nyari penyebab query SQL lambat.
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.