Duplikat adalah kotoran yang paling mahal dampaknya: dua baris yang sama membuat omzet terhitung ganda tanpa terlihat salah di tabel. Pada bagian ini kita membuang duplikat dari data gabungan dua berkas, membedakan tiga wajah duplikat, dan memilih kebijakan yang deterministik. Modul src/dedup.py hanya bergantung pada satu parameter: nama kolom kunci.
Tiga Wajah Duplikat #
Audit menemukan tiga situasi berbeda yang sering dianggap sama padahal tidak:
| Wajah | Ciri | Contoh di data TokoKita |
|---|---|---|
| Duplikat persis dalam berkas | kunci sama, seluruh kolom identik | ORD-2024-0004 dua kali di berkas Juni |
| Duplikat persis lintas berkas | baris Juni muncul lagi di berkas Juli | 3 pesanan Juni diekspor ulang |
| Konflik kunci | kunci sama, isi berbeda | ORD-2024-0046: Juni jumlah=3 Dikirim, Juli jumlah=6 Diproses |
Pembedaan penting karena cara menanganinya bisa berbeda. Duplikat persis aman dibuang; konflik kunci adalah keputusan bisnis, bukan kebersihan teknis.
Kebijakan: Kemunculan Pertama #
Kita memilih kebijakan first occurrence wins: baris pertama yang terlihat dipertahankan, sisanya dibuang. Tiga alasan mendasari pilihan ini.
Pertama, deterministik: hasil tidak bergantung pada urutan acak atau waktu baca berkas kedua. Kedua, dapat dijelaskan: “versi ekspor pertama yang dipakai” mudah dipertanggungjawabkan kepada pemilik data. Ketiga, cocok dengan cerita sumber: berkas Juni adalah catatan pertama, berkas Juli adalah ekspor ulang.
| Kebijakan | Keuntungan | Risiko |
|---|---|---|
| Kemunculan pertama | deterministik, mudah dijelaskan | koreksi terbaru tidak masuk |
| Kemunculan terakhir | koreksi terbaru dipakai | hasil bergantung urutan baca berkas |
| Tolak keras (gagal) | tidak ada keputusan diam-diam | pipeline berhenti untuk satu konflik |
Kebijakan lain sah untuk konteks lain. Yang tidak sah adalah memilih tanpa menuliskannya, karena pembaca tabel tidak akan tahu versi mana yang dipakai.
Urutan: Bersihkan Dulu, Baru Dedup #
Pipeline memanggil dedup setelah pembersihan, bukan sebelumnya. Urutan ini bukan selera, melainkan konsekuensi dari cara duplikat dikenali. Perhatikan satu baris yang sama diekspor dua kali dengan penulisan berbeda:
ORD-2024-0111 ... ELEKTRONIK ... 250.000 ... Dikirim (berkas pertama)
ORD-2024-0111 ... Elektronik ... 250000 ... Dikirim (berkas kedua)Bila dedup berjalan lebih dulu, kedua baris terlihat berbeda dan akan dihitung sebagai konflik. Setelah pembersihan, keduanya menjadi baris yang identik dan terdeteksi sebagai duplikat persis. Kesimpulannya satu kalimat: samakan bentuk dulu, baru bandingkan.
Implementasi dedup_baris #
Fungsi utama modul bekerja dengan satu dictionary terlihat:
def dedup_baris(daftar: list[dict], kunci: str = "order_id") -> tuple[list[dict], StatistikDedup]:
"""Kembalikan (baris unik, statistik) dengan kebijakan kemunculan pertama.
Perbandingan isi memakai kesamaan dict, sehingga dua baris dianggap
duplikat persis hanya bila seluruh kolomnya bernilai sama.
"""
statistik = StatistikDedup(total_masuk=len(daftar))
terlihat: dict[str, dict] = {}
hasil: list[dict] = []
for baris in daftar:
nilai_kunci = baris[kunci]
if nilai_kunci in terlihat:
if baris == terlihat[nilai_kunci]:
statistik.duplikat_persis += 1
else:
statistik.konflik += 1
continue
terlihat[nilai_kunci] = baris
hasil.append(baris)
return hasil, statistikMari kita telaah baris-per-barisnya:
StatistikDedup(total_masuk=len(daftar)): statistik dibuat sejak awal, sehingga selalu ada angka untuk dilaporkan meski daftar kosong.if nilai_kunci in terlihat: pemeriksaan anggota dictionary berlangsung konstan, tidak membandingkan setiap baris dengan setiap baris lain. Dua ratus baris atau dua juta baris, pola waktu tetap linear.baris == terlihat[nilai_kunci]: kesamaan dict membandingkan seluruh kolom. Baris dengan kunci sama dan isi sama dihitungduplikat_persis; isi yang berbeda dihitungkonflik.continue: baris kedua dan seterusnya untuk kunci yang sama dibuang dari hasil, tetapi tetap dihitung di statistik.
Hasil pada Data TokoKita #
Skrip berikut dijalankan nyata terhadap gabungan bersih kedua berkas. Sebelum dedup, setiap baris sudah melewati validasi Bagian 3 dan pembersihan Bagian 4:
juni = clean.bersihkan_semua(lolos("data/pesanan_2024-06.csv"))
juli = clean.bersihkan_semua(lolos("data/pesanan_2024-07.csv"))
unik, statistik = dedup.dedup_baris(juni + juli)Output yang Dihasilkan:
masuk : 197 baris (Juni 91 + Juli 106)
duplikat persis : 7
konflik kunci : 1
keluar : 189 baris unik
konflik ORD-2024-0046:
Juni: jumlah=3, status=Dikirim
Juli: jumlah=6, status=Diproses
keputusan: versi Juni dipertahankan, versi Juli dibuangTujuh duplikat persis terdiri atas dua pasang dalam berkas Juni, dua pasang dalam berkas Juli, dan tiga baris lintas berkas. Satu konflik ORD-2024-0046 diselesaikan dengan kebijakan yang sudah diumumkan, sehingga tidak ada keputusan yang tersembunyi dari pembaca tabel.
UNIQUE sebagai Lapis Ketiga #
Setelah dedup Python, tabel tetap menjaga order_id UNIQUE dari skema Bagian 2. Kunci yang sama kini dijaga tiga lapis:
| Lapis | Penjaga | Bentuk perlindungan |
|---|---|---|
| 1 | validasi Bagian 3 | pola order_id harus sah sebelum diproses |
| 2 | dedup Bagian 6 ini | satu kunci hanya lolos sekali |
| 3 | constraint tabel Bagian 2 | UNIQUE menolak kunci ganda apa pun asalnya |
Bila suatu saat ada bug yang meloloskan dua baris berbeda dengan kunci sama, pemuatan upsert dari Bagian 5 akan memperbarui baris lama alih-alih membiarkan tabel memiliki kunci ganda. Database dengan demikian menjadi jaring terakhir yang tidak bisa dilewati galat Python.
Pendalaman konsep dedup lintas sistem ada di Deduplikasi Data dan Data Consistency. Rujukan struktur data yang dipakai: dictionary Python .
Tabel kini terjamin berisi baris unik dan pemuatan aman diulang. Tiga tahap yang tersisa berurutan: mencatat jejak, menguji, lalu menjalankan. Jejak itulah yang kita pasang berikutnya. Lanjutkan ke Bagian 7: Menambahkan Logging.