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:

WajahCiriContoh di data TokoKita
Duplikat persis dalam berkaskunci sama, seluruh kolom identikORD-2024-0004 dua kali di berkas Juni
Duplikat persis lintas berkasbaris Juni muncul lagi di berkas Juli3 pesanan Juni diekspor ulang
Konflik kuncikunci sama, isi berbedaORD-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.

KebijakanKeuntunganRisiko
Kemunculan pertamadeterministik, mudah dijelaskankoreksi terbaru tidak masuk
Kemunculan terakhirkoreksi terbaru dipakaihasil bergantung urutan baca berkas
Tolak keras (gagal)tidak ada keputusan diam-diampipeline 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:

text
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:

python
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, statistik

Mari kita telaah baris-per-barisnya:

  1. StatistikDedup(total_masuk=len(daftar)): statistik dibuat sejak awal, sehingga selalu ada angka untuk dilaporkan meski daftar kosong.
  2. 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.
  3. baris == terlihat[nilai_kunci]: kesamaan dict membandingkan seluruh kolom. Baris dengan kunci sama dan isi sama dihitung duplikat_persis; isi yang berbeda dihitung konflik.
  4. 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:

python
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:

text
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 dibuang

Tujuh 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:

LapisPenjagaBentuk perlindungan
1validasi Bagian 3pola order_id harus sah sebelum diproses
2dedup Bagian 6 inisatu kunci hanya lolos sekali
3constraint tabel Bagian 2UNIQUE 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.