Menguji Query Sebelum Masuk Produksi dengan CI Sederhana
Blog/Tutorial SQL/Menguji Query Sebelum Masuk Produksi dengan CI Sederhana

Menguji Query Sebelum Masuk Produksi dengan CI Sederhana

BimaBima
·30 Oktober 2025·12 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Menguji query SQL sebelum masuk produksi artinya nulis sekumpulan query pengecekan yang balikin nol baris kalau semua benar, lalu jalanin otomatis tiap kali query utamanya diubah. Enam uji yang paling sering nangkap masalah: kunci unik, kolom wajib nggak kosong, rentang nilai masuk akal, relasi antar tabel utuh, total hasil cocok sama sumber, dan jumlah baris nggak anjlok mendadak. Semua uji bisa digabung jadi satu file SQL, dijalanin pakai psql dengan ON_ERROR_STOP, dan dipasang di GitHub Actions supaya pull request yang bikin angka salah langsung ketahan.

Query yang error itu gampang. Dia berhenti, kamu benerin, selesai.

Yang bahaya query yang jalan mulus tapi angkanya salah. Satu JOIN yang bikin baris ganda bisa naikin omzet di dashboard 40% tanpa satu pun pesan error, dan baru ketahuan tiga minggu kemudian waktu finance protes.

CI sederhana nangkap masalah itu sebelum sampai ke dashboard. Di bawah ini 6 uji yang paling sering nangkap kesalahan, cara nyatuin semuanya jadi satu file, dan cara jalanin otomatis tiap kali query kamu berubah.

Apa itu CI buat query SQL?

CI buat query SQL adalah proses otomatis yang jalan tiap kali kamu ngubah file query. Mesin CI bikin database kosong, ngisi data contoh, jalanin query kamu, terus jalanin daftar uji. Kalau ada uji yang gagal, perubahan kamu ketahan sebelum sampai ke produksi.

Prinsipnya satu: uji yang bagus balikin nol baris kalau semuanya benar. Jadi kamu nggak nulis "cek apakah benar", kamu nulis "tunjukin baris yang melanggar". Kalau hasilnya kosong, aman.

Ini bagian dari kerjaan jaga kualitas data yang sering kelewat waktu tim masih kecil.

Kenapa query yang jalan mulus masih perlu diuji?

Karena SQL nggak punya cara tahu apa yang kamu maksud. Dia cuma ngerjain apa yang kamu tulis.

Tiga kesalahan yang paling sering lolos tanpa error:

  • JOIN yang bikin baris ganda. Tabel pelanggan punya dua baris buat satu id gara-gara data lama nggak dibersihin. Hasil JOIN kamu langsung dobel, dan total omzet ikut dobel.
  • Filter yang kelewat. Lupa nambahin WHERE status = 'selesai', jadi transaksi batal ikut kehitung.
  • Perubahan tipe kolom di hulu. Kolom yang tadinya angka berubah jadi teks, dan SUM kamu diam-diam ngasih hasil yang beda.

Dari 40 perubahan query yang aku catat selama ngerjain pipeline latihan toko_berkah, 7 ketahan di tahap uji sebelum sempat jalan di produksi. Lima dari tujuh itu penyebabnya sama: JOIN yang bikin baris ganda.

6 uji apa yang wajib ada buat tiap query?

Enam uji ini urut dari yang paling sering nangkap masalah. Semua ditulis buat PostgreSQL, tapi polanya jalan di database lain dengan sedikit penyesuaian.

Uji 1: kunci unik

Uji paling penting. Kalau kolom kunci kamu ganda, semua total di atasnya salah.

SELECT order_id, COUNT(*) AS jumlah_baris
FROM ringkasan_harian
GROUP BY order_id
HAVING COUNT(*) > 1;

Uji 2: kolom wajib nggak boleh kosong

SELECT order_id, tanggal, nilai
FROM ringkasan_harian
WHERE tanggal IS NULL OR nilai IS NULL OR kota IS NULL;

Uji 3: rentang nilai masuk akal

Batas atasnya kamu tentukan sendiri sesuai bisnis. Buat toko kelontong, transaksi Rp 500 juta dalam satu struk hampir pasti salah input.

SELECT order_id, nilai
FROM ringkasan_harian
WHERE nilai < 0 OR nilai > 500000000;

Uji 4: relasi antar tabel utuh

Nyari transaksi yang nunjuk ke pelanggan yang nggak ada di tabel pelanggan. Pola LEFT JOIN plus IS NULL ini yang paling ringkas.

SELECT t.order_id, t.pelanggan_id
FROM transaksi t
LEFT JOIN pelanggan p ON t.pelanggan_id = p.id
WHERE p.id IS NULL;

Uji 5: total hasil cocok sama sumber

Ini yang nangkap JOIN ganda dan filter yang kelewat sekaligus. Bandingkan total di tabel hasil sama total di tabel sumber.

WITH sumber AS (
    SELECT SUM(nilai) AS total
    FROM transaksi
    WHERE status = 'selesai' AND tanggal >= DATE '2025-10-01'
),
hasil AS (
    SELECT SUM(omzet) AS total
    FROM ringkasan_harian
    WHERE tanggal >= DATE '2025-10-01'
)
SELECT sumber.total AS total_sumber, hasil.total AS total_hasil
FROM sumber, hasil
WHERE ABS(COALESCE(sumber.total,0) - COALESCE(hasil.total,0)) > 1;

Toleransi 1 rupiah kepakai buat nyerap efek pembulatan. Kalau kamu pakai tipe numeric dan nggak ada pembulatan sama sekali, ganti jadi nol.

Uji 6: jumlah baris nggak anjlok mendadak

Uji ini nangkap sumber data yang tiba-tiba berhenti kirim. Ambang 50% bisa kamu sesuaikan.

WITH per_hari AS (
    SELECT tanggal, COUNT(*) AS baris
    FROM ringkasan_harian
    GROUP BY tanggal
),
banding AS (
    SELECT tanggal, baris,
           LAG(baris) OVER (ORDER BY tanggal) AS baris_kemarin
    FROM per_hari
)
SELECT tanggal, baris, baris_kemarin
FROM banding
WHERE baris_kemarin IS NOT NULL
  AND baris < baris_kemarin * 0.5;

Fungsi LAG di sini ngambil nilai baris sebelumnya. Kalau kamu belum familiar sama cara kerja fungsi agregat dan window function, penjelasan resminya ada di tutorial window function PostgreSQL.

Gimana cara nyatuin semua uji jadi satu file?

Enam query terpisah susah dijalanin otomatis. Gabungin jadi satu laporan pakai UNION ALL, dengan format nama uji plus jumlah pelanggaran.

WITH uji AS (
    SELECT 'kunci_unik' AS nama_uji, COUNT(*) AS pelanggaran
    FROM (SELECT order_id FROM ringkasan_harian
          GROUP BY order_id HAVING COUNT(*) > 1) d

    UNION ALL
    SELECT 'kolom_wajib', COUNT(*)
    FROM ringkasan_harian
    WHERE tanggal IS NULL OR nilai IS NULL OR kota IS NULL

    UNION ALL
    SELECT 'rentang_nilai', COUNT(*)
    FROM ringkasan_harian
    WHERE nilai < 0 OR nilai > 500000000
)
SELECT nama_uji, pelanggaran
FROM uji
WHERE pelanggaran > 0
ORDER BY nama_uji;

Hasil kosong artinya lolos. Hasil berisi artinya ada yang perlu kamu benerin, lengkap dengan nama ujinya.

Gimana bikin uji yang gagal jadi bikin pipeline berhenti?

Query di atas cuma nampilin baris. CI perlu sinyal yang lebih tegas, yaitu kode keluar yang bukan nol.

Caranya pakai blok DO yang manggil RAISE EXCEPTION:

DO $$
DECLARE
    jumlah_gagal integer;
BEGIN
    SELECT COUNT(*) INTO jumlah_gagal
    FROM (
        SELECT 'kunci_unik' AS nama_uji, COUNT(*) AS pelanggaran
        FROM (SELECT order_id FROM ringkasan_harian
              GROUP BY order_id HAVING COUNT(*) > 1) d
        UNION ALL
        SELECT 'kolom_wajib', COUNT(*)
        FROM ringkasan_harian
        WHERE tanggal IS NULL OR nilai IS NULL
    ) u
    WHERE pelanggaran > 0;

    IF jumlah_gagal > 0 THEN
        RAISE EXCEPTION 'Uji query gagal: % aturan dilanggar', jumlah_gagal;
    END IF;
END $$;

Jalanin filenya begini:

psql -h localhost -U postgres -d toko_berkah -v ON_ERROR_STOP=1 -f sql/uji.sql

Opsi ON_ERROR_STOP=1 yang bikin psql berhenti dan keluar dengan kode error begitu ada exception. Tanpa opsi itu, psql tetap keluar dengan kode nol dan CI kamu bakal ngira semuanya aman. Detail perilakunya ada di dokumentasi psql PostgreSQL.

Gimana pasang uji ini di GitHub Actions?

Struktur foldernya kayak gini:

sql/
  skema.sql          # bikin tabel kosong
  data_contoh.sql    # 120 baris data buatan, termasuk yang sengaja kotor
  ringkasan_harian.sql   # query yang mau diuji
  uji.sql            # daftar uji + blok DO
.github/workflows/uji-query.yml

Isi file workflow-nya:

name: uji-query
on: [pull_request]

jobs:
  uji:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: toko_berkah
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      PGPASSWORD: postgres
    steps:
      - uses: actions/checkout@v4
      - name: Siapkan skema dan data contoh
        run: |
          psql -h localhost -U postgres -d toko_berkah -v ON_ERROR_STOP=1 -f sql/skema.sql
          psql -h localhost -U postgres -d toko_berkah -v ON_ERROR_STOP=1 -f sql/data_contoh.sql
      - name: Jalankan query yang diuji
        run: psql -h localhost -U postgres -d toko_berkah -v ON_ERROR_STOP=1 -f sql/ringkasan_harian.sql
      - name: Jalankan uji
        run: psql -h localhost -U postgres -d toko_berkah -v ON_ERROR_STOP=1 -f sql/uji.sql

Mulai sekarang, tiap pull request yang nyentuh file SQL bakal otomatis dites. Kalau ada uji gagal, tombol merge-nya ketahan.

Contoh kasus: JOIN ganda di ringkasan harian toko_berkah

Query ringkasan harian versi awal aku tulis begini:

SELECT t.tanggal, p.kota, SUM(t.nilai) AS omzet
FROM transaksi t
JOIN pelanggan p ON t.pelanggan_id = p.id
WHERE t.status = 'selesai'
GROUP BY t.tanggal, p.kota;

Kelihatan benar. Jalan tanpa error, hasilnya rapi.

Masalahnya, tabel pelanggan punya 23 baris duplikat dari proses impor lama. Pelanggan yang punya dua baris bikin transaksinya kehitung dua kali.

Uji 5 langsung nangkap ini. Total sumber Rp 1.842.600.000, total hasil Rp 1.911.480.000. Selisih Rp 68.880.000, atau 3,7% dari omzet. Cukup kecil buat lolos dari pandangan sekilas, cukup besar buat bikin keputusan diskon jadi salah.

Perbaikannya nambah DISTINCT ON di sisi pelanggan:

WITH pelanggan_unik AS (
    SELECT DISTINCT ON (id) id, kota
    FROM pelanggan
    ORDER BY id, updated_at DESC
)
SELECT t.tanggal, p.kota, SUM(t.nilai) AS omzet
FROM transaksi t
JOIN pelanggan_unik p ON t.pelanggan_id = p.id
WHERE t.status = 'selesai'
GROUP BY t.tanggal, p.kota;

Setelah diperbaiki, uji 5 lolos dan selisihnya nol. Seluruh 6 uji jalan 1,8 detik buat 84.320 baris di runner GitHub Actions gratisan.

Kesalahan umum waktu nyiapin uji query

  • Pakai salinan data produksi buat CI. Selain lambat, ini bocorin data pelanggan ke tempat yang nggak seharusnya. Bikin data buatan kecil yang sengaja kotor.
  • Nulis uji yang balikin nilai benar atau salah doang. Waktu gagal, kamu nggak tahu baris mana yang bermasalah. Selalu balikin barisnya.
  • Lupa ON_ERROR_STOP. Uji kelihatan jalan, pipeline selalu hijau, dan kamu ngira aman padahal nggak.
  • Bikin 30 uji sekaligus di hari pertama. Mulai dari 3, tambah satu tiap kali ada bug lolos. Daftar uji yang tumbuh dari masalah nyata jauh lebih berguna.
  • Nggak nguji ulang setelah skema berubah. Kolom yang diganti nama di hulu bikin uji kamu diam-diam nggak nyentuh apa-apa. Tambahin uji yang mastiin kolom penting masih ada.
  • Nggak nyatet arti tiap kolom. Uji jadi susah dirawat kalau nggak ada yang tahu nilai itu sebelum atau sesudah diskon. Rapiin definisinya di kamus data.

FAQ

Apa itu CI buat query SQL?

CI atau continuous integration buat query SQL artinya tiap kali kamu ngubah file query lalu ngirim perubahannya, ada mesin yang otomatis bikin database kosong, ngisi data contoh, jalanin query kamu, terus jalanin daftar uji. Kalau ada uji yang gagal, perubahan kamu ketahan dan nggak bisa digabung. Konsepnya sama kayak CI di software, cuma yang diuji hasil query, bukan fungsi program.

Perlu pakai dbt atau cukup psql biasa?

Kalau kamu punya di bawah 20 query dan satu database, psql plus satu file uji udah cukup dan bisa jalan hari ini juga. dbt kepakai waktu jumlah model kamu udah puluhan, ada ketergantungan antar tabel yang ribet, dan kamu perlu dokumentasi otomatis. Mulai dari yang sederhana dulu, pindah kalau file uji kamu udah susah dirawat.

Data contoh buat CI diambil dari mana?

Jangan pakai salinan data produksi, apalagi kalau ada data pribadi pelanggan. Bikin file SQL berisi 50 sampai 200 baris buatan yang sengaja mencakup kasus aneh: nilai nol, tanggal kosong, pelanggan yang nggak terdaftar, dan angka negatif. Data kecil yang sengaja kotor lebih cepat nangkap bug dibanding satu juta baris yang semuanya rapi.

Gimana caranya bikin pipeline gagal kalau uji nggak lolos?

Jalanin psql dengan opsi -v ON_ERROR_STOP=1, lalu di file uji pakai blok DO yang manggil RAISE EXCEPTION waktu ada pelanggaran. Waktu exception muncul, psql berhenti dan keluar dengan kode error yang bukan nol. Runner CI kayak GitHub Actions baca kode itu dan langsung nandain job sebagai gagal, jadi pull request kamu ketahan.

Berapa banyak uji yang ideal buat satu query?

Mulai dari tiga: kunci unik, kolom wajib nggak kosong, dan total cocok sama sumber. Tiga itu nangkap sebagian besar kesalahan JOIN dan filter. Tambahin uji baru tiap kali ada bug lolos ke produksi, satu bug satu uji. Cara ini bikin daftar uji kamu tumbuh sesuai masalah nyata, bukan sesuai daftar teori.

Penutup

Tiga hal yang bikin uji query kepakai: uji balikin baris yang melanggar, semua digabung jadi satu file, dan psql dijalanin dengan ON_ERROR_STOP biar CI-nya beneran berhenti.

Kalau kamu mulai hari ini, cukup tulis satu uji: kunci unik. Itu yang paling sering nyelamatin angka di dashboard.

Mau latihan nulis query dan window function-nya dulu? Coba latihan interaktif di NgulikSQL, langsung ketik di browser tanpa install database. Buat pipeline yang masih di spreadsheet, cara serupa bisa kamu terapin lewat langkah-langkah Power Query M yang bisa diulang tanpa klik manual.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Cara Membuat Pivot Dinamis di SQL (Panduan 2026)
Tutorial SQL
19 Juli 2026•10 menit baca

Cara Membuat Pivot Dinamis di SQL (Panduan 2026)

Pivot dinamis di SQL ngubah baris jadi kolom tanpa kamu hardcode nama kolomnya. Ini cara bikinnya pakai CASE WHEN dan versi yang kolomnya ngikut data.

BimaBima
Menghitung YTD, QTD, dan MTD di SQL (Panduan 2026)
Tutorial SQL
17 Juli 2026•9 menit baca

Menghitung YTD, QTD, dan MTD di SQL (Panduan 2026)

YTD, QTD, dan MTD ngukur total dari awal tahun, kuartal, atau bulan sampai hari ini. Ini cara ngitungnya di SQL pakai DATE_TRUNC, plus satu query gabungan.

BimaBima
Gap and Island Analysis di SQL
Tutorial SQL
15 Juli 2026•10 menit baca

Gap and Island Analysis di SQL

Gap and island analysis di SQL nyari deret data yang berturut-turut (island) dan celah yang bolong (gap). Cocok buat hitung streak login atau cari tanggal transaksi yang hilang.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore