Data quality validation adalah memeriksa data dengan aturan yang jelas sebelum ada yang memakainya, lalu memutuskan apa yang terjadi kalau aturannya dilanggar. Bayangin petugas penerima barang di gudang minimarket. Tiap truk datang, dia mencocokkan jumlah dus dengan surat jalan, mengecek tanggal kedaluwarsa, label harga, dan barang yang terkirim dua kali. Barang yang aneh dipisah ke rak retur, sisanya masuk rak. Kalau truknya datang kosong, penerimaan dihentikan dan dia menelepon pengirim. Pipeline Toko Buku butuh petugas yang sama: cek-ceknya, rak retur bernama karantina, dan tombol berhenti.
🧪 Tebakan cepat: cek unique dijalankan pada kolom
item_iddiraw_order_lines. Item 9 adalah salinan item 8 dari job yang di-retry. Ceknya lolos atau gagal? Tebak dulu, jawabannya ada di bagian "Cek adalah query yang mencari pelanggar".
Enam dimensi kualitas data
Tiap cek mengukur satu dimensi kualitas data:
| Dimensi | Pertanyaannya | Di Toko Buku |
|---|---|---|
| Completeness | apakah semua data yang diharapkan ada? | kota Maya NULL; jumlah baris raw = sumber |
| Validity | apakah nilainya sesuai format dan aturan? | harga harus antara 10.000 dan 500.000 |
| Accuracy | apakah nilainya benar menurut kenyataan? | harga 89.000 untuk buku 95.000 lolos validity, tapi tetap salah |
| Consistency | apakah data cocok antar tabel dan sistem? | tiap book_id di pesanan ada di books |
| Timeliness | apakah datanya cukup baru? | pesanan terbaru gak lebih tua dari 24 jam |
| Uniqueness | apakah ada yang dobel? | satu pasangan pesanan dan buku cuma sekali |
Validity yang paling sering dicek: nilai yang sesuai format dan aturan. Accuracy lebih susah, karena butuh sumber kebenaran untuk dibandingkan, misalnya katalog.
Cek adalah query yang mencari pelanggar
Cara paling sederhana menulis cek: satu SELECT yang mencari baris yang melanggar aturan. Lolos kalau hasilnya 0 baris. Semua di bawah ini jalan di SQLite (sama dengan Lab dan Playground) pada raw_order_lines, 19 baris mentah dari pelajaran ETL.
-- unique: satu baris per pasangan (order_id, book_id)
SELECT item_id, order_id, book_id, qty
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id, book_id ORDER BY item_id) AS ke
FROM raw_order_lines)
WHERE ke > 1;
-- 9 | 6 | 2 | 2
-- not_null: kota harus terisi
SELECT item_id, customer, city FROM raw_order_lines WHERE city IS NULL;
-- 11 | Maya Sari | NULL
-- 19 | Maya Sari | NULL
-- accepted range: harga masuk akal
SELECT item_id, title, price FROM raw_order_lines
WHERE price NOT BETWEEN 10000 AND 500000;
-- 16 | Ronggeng Dukuh Paruk | 950000
Tiga cek, tiga baris kotor tertangkap. Kalau cek unique ditaruh di item_id, hasilnya 0 baris: lolos. Job yang di-retry menyimpan salinannya dengan id baru (9), jadi item_id memang unik. Itu jawaban tebakannya. Cek unique harus memakai kunci bisnis, kolom yang di dunia nyata memang gak boleh dobel: satu buku dalam satu pesanan.
Tiga cek lagi, dan ketiganya lolos di data hari ini:
| Cek | Query mencari | Hasil |
|---|---|---|
| relationships | order_items yang book_id-nya gak ada di books (LEFT JOIN ... WHERE b.id IS NULL) |
0 baris |
| row count | jumlah baris sumber beda dengan raw | 19 = 19 |
| freshness | pesanan terbaru lebih tua dari 24 jam | dicek Sabtu 06:00: 19,5 jam |
Cek yang lolos tetap berguna. raw_order_lines dibangun dengan JOIN ke books. Kalau suatu hari admin menghapus Ronggeng Dukuh Paruk dari katalog, JOIN itu diam-diam membuang 4 baris pesanannya: relationships menemukan 4 pelanggar, dan row count jadi 19 lawan 15. Freshness juga bisa berubah tanpa ada yang rusak: dicek Senin 06:00, pesanan terbaru (Jumat 10:30) sudah 67,5 jam.
Di mana cek dijalankan
- Saat load, sebagai gerbang. Raw mendarat, cek jalan, baru
penjualandibangun. Di pipeline Python atau Airflow ini satu langkah sendiri sebelum transform. - Di dbt (pelajaran dbt nanti). Cek ditulis di YAML di samping model. dbt Core 1.12 punya empat cek bawaan:
unique,not_null,accepted_values,relationships; paketdbt_utils1.4 menambahaccepted_rangedanunique_combination_of_columns.dbt testmenjalankannya,dbt buildmenjalankan model dan cek berurutan dan melewati model hilir kalau ceknya gagal. Tiap cek dbt juga sebuahSELECTyang mencari baris gagal.
models:
- name: raw_order_lines
data_tests:
- dbt_utils.unique_combination_of_columns:
arguments:
combination_of_columns: [order_id, book_id]
columns:
- name: city
data_tests:
- not_null:
config:
severity: warn
- name: price
data_tests:
- dbt_utils.accepted_range:
arguments:
min_value: 10000
max_value: 500000
- Di Python, dengan Great Expectations (GX Core 1.24.0). Aturannya disebut Expectation: aturan deklaratif seperti "kolom ini harus unik" atau "nilai harus antara X dan Y". Hasilnya laporan validasi yang bisa dipasang sebagai gerbang di pipeline.
import great_expectations as gx
import pandas as pd
df = pd.read_csv("order_lines.csv")
context = gx.get_context(mode="ephemeral")
batch = (
context.data_sources.add_pandas("toko")
.add_dataframe_asset("order_lines")
.add_batch_definition_whole_dataframe("semua")
.get_batch(batch_parameters={"dataframe": df})
)
suite = gx.ExpectationSuite(name="order_lines")
suite.add_expectation(gx.expectations.ExpectCompoundColumnsToBeUnique(column_list=["order_id", "book_id"]))
suite.add_expectation(gx.expectations.ExpectColumnValuesToNotBeNull(column="city"))
suite.add_expectation(gx.expectations.ExpectColumnValuesToBeBetween(column="price", min_value=10000, max_value=500000))
hasil = batch.validate(suite)
print(hasil.success)
for r in hasil.results:
print(r.expectation_config.type, r.success, r.result["unexpected_count"])
False
expect_compound_columns_to_be_unique False 2
expect_column_values_to_not_be_null False 2
expect_column_values_to_be_between False 1
ExpectCompoundColumnsToBeUniquemenghitung 2: GX menandai kedua baris yang kembar (8 dan 9), query di atas cuma salinan keduanya (9).- Soda Core (4.26.0) mengerjakan hal yang sama dengan data contract di YAML: cek
missing,invalid,duplicate,freshness,row_count.
Kalau cek gagal: hentikan atau karantina
Tiap cek diberi severity. error artinya datanya gak boleh lewat; warn artinya catat dan beri tahu, tapi jalan terus. Kota Maya yang NULL itu warn: penjualannya asli, kotanya diisi Tidak diketahui. Duplikat dan harga 950.000 itu error. Untuk cek error ada dua pilihan:
- Hentikan pipeline. Langkah gerbang melempar error,
penjualangak dibangun ulang, laporan pemilik masih memakai data kemarin, dan yang jaga dapat notifikasi. Cocok kalau kesalahannya menyangkut semua baris: ekstrak kosong, kolom hilang. - Karantina. Baris yang gagal dipindah ke tabel
karantinabersama nama ceknya, sisanya jalan terus:
CREATE TABLE karantina AS
SELECT item_id, order_id, book_id, qty, price, 'unique' AS cek
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id, book_id ORDER BY item_id) AS ke
FROM raw_order_lines)
WHERE ke > 1
UNION ALL
SELECT item_id, order_id, book_id, qty, price, 'accepted_range'
FROM raw_order_lines
WHERE price NOT BETWEEN 10000 AND 500000;
Lalu penjualan dibangun dari raw_order_lines dengan WHERE item_id NOT IN (SELECT item_id FROM karantina). Hasilnya karantina 2 baris (item 9 dan 16), penjualan 17 baris, dan cek rekonsiliasi: 19 baris raw = 17 + 2, gak ada yang hilang diam-diam. Harganya: laporan pemilik kurang 1 buku (pesanan Rizky) sampai ada yang membetulkan item 16. Kalau aturan perbaikannya pasti, seperti harga dari katalog di pelajaran ETL, perbaiki saja di transform. Karantina untuk yang gak bisa dibetulkan otomatis.
⚠️ Jebakan yang sering ketemu
- Unique di id otomatis.
item_idselalu unik, jadi duplikat dari retry lolos. Cek kunci bisnisnya:(order_id, book_id). - Semua dijadikan error. Kalau kota
NULLmenghentikan pipeline, laporan berhenti gara-gara satu pelanggan yang daftar lewat Google. Pilih severity per cek. - Freshness yang gak kenal kalender. Batas 24 jam akan berbunyi tiap Senin pagi kalau tokonya sepi di akhir pekan. Alarm yang terlalu sering bunyi akhirnya diabaikan.
- Karantina tanpa pemilik. Baris yang dikarantina harus ada yang memeriksa dan mengembalikan. Kalau enggak, laporan kurang terus tanpa ada yang tahu.
TL;DR: Kualitas data diukur per dimensi: completeness, validity (sesuai format dan aturan), accuracy, consistency, timeliness, uniqueness. Satu cek = satu SELECT yang mencari pelanggar; lolos kalau 0 baris. Di Toko Buku: unique pada kunci bisnis (order_id, book_id) menangkap item 9 (unique pada item_id malah lolos), not_null menangkap 2 baris Maya, accepted range 10.000 sampai 500.000 menangkap item 16; relationships, row count (19 = 19), dan freshness lolos hari ini. Cek jalan sebagai gerbang saat load, sebagai tes di dbt (unique, not_null, accepted_values, relationships), atau dengan Great Expectations (Expectation seperti ExpectColumnValuesToBeBetween) dan Soda Core. Kalau gagal: severity warn jalan terus dan memberi tahu, error menghentikan pipeline atau memindahkan barisnya ke karantina, lalu dicek bahwa raw = penjualan + karantina (19 = 17 + 2).