TL;DR
Data bermasalah biasanya ketahuan dari 10 tanda: baris duplikat, kolom kosong, format tanggal campur, satuan beda, kategori dobel nama, angka negatif di kolom yang harusnya positif, total yang gak cocok sama sumber, ID yatim, outlier ekstrem, dan timestamp masa depan. Semua bisa dicek pakai query SQL pendek sebelum satu chart pun dibuat.
Data bermasalah hampir selalu ngasih tanda sebelum kamu sempat bikin satu chart pun. Baris duplikat, kolom kosong, tanggal masa depan, kategori yang ditulis tiga versi berbeda.
Masalahnya, tanda-tanda ini gak muncul sendiri di layar. Kamu harus nyari.
Aku pernah kelewat satu tanda doang dan hasilnya laporan penjualan Q3 salah 14 persen. Bukan karena rumusnya keliru, tapi karena ada 812 baris duplikat yang ikut ke-SUM.
Berikut sepuluh tanda yang paling sering muncul, plus query buat ngecek masing-masing.
Karena kesalahan di data mentah bakal kebawa ke setiap angka setelahnya. Satu baris duplikat bikin total penjualan naik. Satu tanggal salah format bikin tren bulanan bergeser. Analisis paling rapi pun gak bisa nyelametin input yang rusak, dan biasanya baru ketahuan pas laporan udah dipresentasiin ke atasan.
Pengecekan ini bagian dari data quality, yaitu praktik mastiin data layak dipakai buat ambil keputusan. Gak butuh tool mahal. Sepuluh query pendek udah cukup buat nangkep mayoritas masalah.
Duplikat muncul gara-gara export dobel, sinkronisasi yang jalan dua kali, atau kasir yang nge-submit ulang waktu koneksi lemot. Efeknya paling terasa di angka total dan rata-rata.
SELECT id_transaksi, COUNT(*) AS jumlah
FROM toko_berkah.transaksi
GROUP BY id_transaksi
HAVING COUNT(*) > 1;
Kalau hasilnya kosong, aman. Kalau keluar baris, catat berapa banyak sebelum kamu hapus. Angka itu penting buat ngejelasin kenapa total kamu beda sama total sistem.
Kolom kosong di field pendukung masih bisa ditolerir. Kolom kosong di ID pelanggan atau tanggal transaksi bikin analisis apa pun jadi gak valid.
SELECT
COUNT(*) AS total_baris,
COUNT(*) - COUNT(id_pelanggan) AS pelanggan_kosong,
COUNT(*) - COUNT(kota) AS kota_kosong,
COUNT(*) - COUNT(total_bayar) AS bayar_kosong
FROM toko_berkah.transaksi;
COUNT dengan nama kolom cuma ngitung nilai yang gak NULL. Selisihnya sama COUNT(*) adalah jumlah yang kosong. Cara ini jalan di hampir semua database, sesuai perilaku standar fungsi agregat yang dijelasin di dokumentasi PostgreSQL.
Ini tanda paling licik. Data dari kasir pakai format 25/12/2025, data dari marketplace pakai 2025-12-25, dan ada satu file lagi pakai Dec 25, 2025.
Kalau kolomnya bertipe teks, database gak akan protes. Chart kamu yang bakal ngaco karena urutan bulannya acak.
Cek dengan lihat panjang karakter yang berbeda-beda:
SELECT LENGTH(tanggal_teks) AS panjang, COUNT(*) AS baris
FROM toko_berkah.transaksi_raw
GROUP BY LENGTH(tanggal_teks)
ORDER BY baris DESC;
Lebih dari satu nilai panjang? Berarti formatnya belum seragam.
Kolom berat isinya campuran gram dan kilogram. Kolom harga campuran rupiah penuh dan rupiah dalam ribuan. Ini sering kejadian waktu dua sumber digabung tanpa dicek.
Gejalanya kelihatan dari sebaran yang aneh, misalnya ada harga 25 dan ada harga 25000 buat produk yang sama. Cek pakai rentang nilai per produk:
SELECT id_produk,
MIN(harga_satuan) AS harga_min,
MAX(harga_satuan) AS harga_max
FROM toko_berkah.transaksi
GROUP BY id_produk
HAVING MAX(harga_satuan) > MIN(harga_satuan) * 100;
"Bandung", "bandung", "Bandung " dengan spasi di belakang. Buat kamu itu satu kota. Buat GROUP BY itu tiga kota berbeda.
SELECT LOWER(TRIM(kota)) AS kota_bersih,
COUNT(DISTINCT kota) AS varian_penulisan,
COUNT(*) AS baris
FROM toko_berkah.transaksi
GROUP BY LOWER(TRIM(kota))
HAVING COUNT(DISTINCT kota) > 1;
Query ini nunjukin kota mana yang punya lebih dari satu cara penulisan. Perbaikannya gampang, yang susah itu nyadar masalahnya ada.
Qty negatif, total bayar negatif, umur pelanggan negatif. Kadang ini valid karena retur, tapi lebih sering karena salah input atau kolom yang ketuker.
SELECT COUNT(*) AS baris_negatif
FROM toko_berkah.transaksi
WHERE total_bayar < 0 OR qty < 0;
Kalau ternyata retur beneran, pastiin ada kolom penanda transaksinya. Jangan biarkan retur dan salah input campur di satu kolom yang sama.
Ini pengecekan paling sederhana dan paling sering dilewat. Ambil total penjualan bulan lalu dari query kamu, terus bandingin sama angka yang keluar dari sistem kasir atau laporan finance.
Beda 0,1 persen masih wajar karena pembulatan. Beda 5 persen berarti ada yang hilang atau kehitung dobel di salah satu sisi.
Kalau kamu kerja di spreadsheet, SUMIF bisa dipakai buat ngitung total per bulan lalu dibandingin manual sama laporan resmi.
ID produk di tabel transaksi yang gak ada di tabel produk. Artinya ada produk yang kehapus, atau ada typo di ID.
SELECT t.id_produk, COUNT(*) AS baris
FROM toko_berkah.transaksi t
LEFT JOIN toko_berkah.produk p ON p.id_produk = t.id_produk
WHERE p.id_produk IS NULL
GROUP BY t.id_produk;
Kalau kamu lanjut analisis pakai INNER JOIN, baris-baris ini bakal hilang diam-diam. Total penjualan kamu jadi lebih kecil dari kenyataan tanpa ada peringatan apa pun.
Satu transaksi Rp980.000.000 di toko kelontong yang rata-rata transaksinya Rp47.000. Ini outlier, nilai yang jauh menyimpang dari sebaran normal.
SELECT id_transaksi, total_bayar
FROM toko_berkah.transaksi
WHERE total_bayar > (SELECT AVG(total_bayar) + 3 * STDDEV_SAMP(total_bayar)
FROM toko_berkah.transaksi)
ORDER BY total_bayar DESC;
Gak semua outlier itu salah. Bisa aja beneran ada pembelian borongan buat hajatan. Yang penting kamu tau dia ada sebelum dia ngerusak rata-rata.
Transaksi tercatat tanggal 12 Maret 2027 padahal sekarang Desember 2025. Biasanya gara-gara zona waktu server, atau jam di perangkat kasir yang salah setel.
SELECT COUNT(*) AS tanggal_aneh
FROM toko_berkah.transaksi
WHERE tanggal > CURRENT_DATE OR tanggal < DATE '2020-01-01';
Batas bawahnya kamu sesuaikan sama tanggal toko mulai beroperasi. Apa pun yang di luar rentang itu patut dicurigai.
Aku jalanin sepuluh pengecekan ini ke dataset toko_berkah, 18.400 baris transaksi dari Januari sampai November 2025. Hasilnya:
| Tanda | Baris kena | % dari total |
|---|---|---|
| Duplikat id_transaksi | 812 | 4,4% |
| id_pelanggan kosong | 1.203 | 6,5% |
| Kota dobel penulisan | 2.940 | 16,0% |
| total_bayar negatif | 37 | 0,2% |
| ID produk yatim | 64 | 0,3% |
| Outlier di atas 3 SD | 21 | 0,1% |
| Tanggal masa depan | 9 | 0,05% |
Yang bikin kaget bukan angka duplikatnya, tapi kota dobel penulisan yang kena 16 persen baris. Sebelum dibersihin, laporan per kota nampilin 31 kota. Setelah TRIM dan LOWER, ternyata cuma 19 kota beneran.
Dua belas kota "hantu" itu muncul karena spasi dan kapital. Kalau laporan itu dipakai buat mutusin lokasi cabang baru, keputusannya bakal salah sasaran.
Total penjualan setelah duplikat dibuang turun dari Rp1,42 miliar jadi Rp1,36 miliar. Selisih Rp62 juta yang tadinya bakal masuk ke slide presentasi.
Buat tabel di bawah 1 juta baris, 15 sampai 20 menit udah cukup buat jalanin sepuluh query pengecekan di artikel ini. Simpan query-nya jadi satu file, terus jalanin tiap kali kamu terima dump data baru. Yang makan waktu itu bukan ngejalaninnya, tapi mutusin mana masalah yang harus diperbaiki di sumber dan mana yang cukup difilter di query.
Prinsipnya sama. Duplikat kamu cek pakai COUNTIF, kolom kosong pakai COUNTBLANK, kategori dobel nama pakai UNIQUE plus TRIM. Yang beda cuma alatnya. Untuk data di atas 100 ribu baris, Excel bakal berat dan lebih enak dipindah ke database dulu sebelum dicek.
Data kotor itu formatnya berantakan tapi informasinya masih benar, misalnya kota ditulis "Bandung" dan "bandung". Data salah itu isinya memang keliru, misalnya transaksi tercatat Rp5.000.000 padahal aslinya Rp500.000. Data kotor bisa kamu benerin sendiri lewat query. Data salah harus dikonfirmasi ke orang yang input.
Perlu, dan cukup satu tab spreadsheet. Isinya tanggal cek, nama tabel, tanda yang ketemu, jumlah baris kena, dan keputusan kamu. Waktu tiga bulan lagi ada yang nanya kenapa angka Oktober beda sama sistem, catatan ini yang nyelametin kamu dari tebak-tebakan.
Jangan langsung dibuang. Hitung dulu berapa persen baris yang kena. Kalau di bawah 2 persen dan gak nyentuh kolom kunci, kamu bisa lanjut analisis sambil catat batasannya. Kalau di atas 10 persen atau kena kolom yang jadi dasar keputusan, hentikan dulu dan benerin di sumbernya.
Sepuluh tanda ini bisa kamu cek dalam waktu kurang dari 20 menit. Simpan query-nya jadi satu file, jalanin tiap kali data baru masuk.
Dua hal yang paling sering nyelametin laporan: cek duplikat dan cek kota dobel penulisan. Dua itu doang udah nangkep sebagian besar masalah di dataset UMKM.
Mau versi query yang lebih lengkap plus penjelasan tiap baris? Lanjut ke SQL buat cek kualitas data. Kalau kamu udah pakai pipeline dan mau pengecekannya jalan otomatis, mampir ke dbt tests untuk kualitas data.
Mulai dari satu tabel yang paling sering kamu pakai. Jalanin query pertama hari ini juga.
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.