Data toko online kita tersebar di banyak tempat: CSV hasil ekspor dari sistem operasional, database transaksi pesanan, dan API logistik pengiriman. Tim manajemen tidak bisa membaca ketiga sumber tersebut satu per satu — mereka membutuhkan satu tabel ringkas yang bisa diquery: bersih, bertipe data benar, dan siap dianalisis. ETL (Extract, Transform, Load) dan variasi mudanya ELT (Extract, Load, Transform) adalah dua pola standar untuk menjembatani kebutuhan tersebut. Chapter ini membahas definisi keduanya, kapan masing-masing dipilih, evolusi yang menggeser transformasi ke dalam warehouse, lalu ditutup dengan mini pipeline Python yang benar-benar dijalankan dari CSV ke SQLite.
Definisi: ETL dan ELT #
Pada ETL, data diekstrak dari sumber, ditransformasi di luar warehouse (di server pipeline atau tool tertentu), dan hasil yang sudah bersih baru dimuat ke warehouse. Pada ELT, urutan dua tahap terakhir dibalik: data diekstrak lalu langsung dimuat dalam kondisi mentah, dan transformasi berjalan di dalam warehouse setelah data tersimpan — umumnya berupa query SQL yang dijalankan berkala.
Perhatikan kedua baris diagram di atas: baris pertama (ETL) menempatkan Transform sebelum Load, sedangkan baris kedua (ELT) menempatkan Transform sebagai tahap terakhir di dalam warehouse. Perbedaan urutan tersebut terasa dampaknya pada praktik — pada ETL, warehouse hanya menerima data siap pakai; pada ELT, warehouse menyimpan data mentah dan query transformasi dijalankan di sana.
Kapan ETL, Kapan ELT #
| Aspek | ETL | ELT |
|---|---|---|
| Posisi transform | server pipeline, sebelum load | di dalam warehouse, setelah load |
| Kondisi data di warehouse | sudah bersih dan siap pakai | mentah, dibersihkan saat query |
| Beban komputasi | di server pipeline | di engine warehouse |
| Cocok untuk | sumber kotor yang butuh pembersihan berat sebelum masuk | sumber relatif rapi dengan warehouse yang kuat |
| Contoh pelaksana | skrip Python, job Spark, task Airflow | query SQL, dbt |
Evolusi menuju ELT bergerak seiring perkembangan warehouse. Dulu, warehouse berbasis baris on-premise mahal dan lambat untuk query besar, sehingga masuk akal membersihkan data di luar lebih dulu. Warehouse modern menyimpan data per kolom, berjalan di cloud, dan memisahkan biaya penyimpanan dari biaya komputasi — konsekuensinya, memuat data mentah lebih dulu menjadi murah, dan transformasi berupa SQL di dalam warehouse justru lebih cepat untuk jutaan baris. Bonus penting dari pola ELT: data mentah tetap tersimpan di warehouse, sehingga ketika aturan transformasi berubah, kita cukup menjalankan ulang query tanpa mengekstrak ulang dari sumber.
Mini Pipeline: dari CSV ke SQLite #
Teori di atas akan kita buktikan dengan ETL mini untuk pesanan toko. Sumbernya adalah CSV hasil ekspor dengan berbagai masalah klasik: spasi berlebih di sekitar nama, kapitalisasi tidak konsisten, satu pesanan tanpa kota, dan satu pesanan berstatus dibatalkan yang tidak boleh masuk laporan pendapatan.
pesanan_id,pelanggan,kota,status,subtotal
1001, budi ,Jakarta,new,250000
1002,siti,Bandung,shipped ,175000
1003,dewi,,new,320000
1004,andi,,cancelled,90000
1005, siti ,Surabaya,delivered,410000Pipeline berikut menjalankan ketiga tahap secara berurutan: ekstrak dengan modul csv, transformasi dalam satu perulangan, lalu pemuatan ke tabel SQLite. Seluruh blok pemuatan dibungkus satu transaksi sehingga sebagian baris tidak pernah tersimpan setengah jalan.
import csv
import sqlite3
# 1) Extract: baca CSV mentah dari sistem operasional toko
with open("pesanan_mentah.csv", newline="", encoding="utf-8") as berkas:
baris_mentah = list(csv.DictReader(berkas))
print("Baris terekstrak:", len(baris_mentah))
print("Contoh baris mentah:", baris_mentah[0])
# 2) Transform: rapikan string, buang pesanan dibatalkan
baris_bersih = []
for baris in baris_mentah:
if baris["status"].strip().lower() == "cancelled":
continue
baris_bersih.append({
"pesanan_id": int(baris["pesanan_id"]),
"pelanggan": baris["pelanggan"].strip().title(),
"kota": baris["kota"].strip().title() or "Tidak diketahui",
"status": baris["status"].strip().lower(),
"subtotal": int(baris["subtotal"]),
})
print("Baris setelah transform:", len(baris_bersih))
# 3) Load: simpan ke tabel SQLite (warehouse mini)
koneksi = sqlite3.connect(":memory:")
koneksi.execute(
"CREATE TABLE pesanan_bersih ("
"pesanan_id INTEGER PRIMARY KEY, pelanggan TEXT, kota TEXT, "
"status TEXT, subtotal INTEGER)"
)
with koneksi:
koneksi.executemany(
"INSERT INTO pesanan_bersih VALUES (:pesanan_id, :pelanggan, :kota, "
":status, :subtotal)",
baris_bersih,
)
for baris in koneksi.execute("SELECT * FROM pesanan_bersih ORDER BY pesanan_id"):
print(baris)
total = koneksi.execute("SELECT SUM(subtotal) FROM pesanan_bersih").fetchone()[0]
print("Total subtotal tersimpan:", total)
koneksi.close()Output yang Dihasilkan:
Baris terekstrak: 5
Contoh baris mentah: {'pesanan_id': '1001', 'pelanggan': ' budi ', 'kota': 'Jakarta', 'status': 'new', 'subtotal': '250000'}
Baris setelah transform: 4
(1001, 'Budi', 'Jakarta', 'new', 250000)
(1002, 'Siti', 'Bandung', 'shipped', 175000)
(1003, 'Dewi', 'Tidak diketahui', 'new', 320000)
(1005, 'Siti', 'Surabaya', 'delivered', 410000)
Total subtotal tersimpan: 1155000Mari kita telaah baris-baris kode di atas:
- Baris 5-6 (Extract):
csv.DictReadermembaca CSV dan mengubah setiap baris menjadi dictionary bertipe string — perhatikan output baris mentah:' budi 'masih mengandung spasi dan'250000'belum berupa angka. - Baris 12-14 (filter): pesanan berstatus
cancelleddiskip dengancontinue, sehingga lima baris masuk tetapi hanya empat baris keluar dari tahap transform. - Baris 16-20 (pembersihan dan konversi tipe):
strip().title()merapikan nama pelanggan, ekspresior "Tidak diketahui"mengganti kota kosong milik pesanan 1003, danint()mengubah subtotal dari string menjadi bilangan bulat. - Baris 25-30 (Load, bagian schema): tabel tujuan
pesanan_bersihdibuat dengan tipe kolom eksplisit sebelum data masuk. - Baris 31-36 (Load, bagian transaksi): blok
with koneksimembuka satu transaksi;executemanymengirim seluruh baris dalam satu panggilan, lalu transaksi ditutup otomatis denganCOMMIT. - Baris 37-40 (verifikasi): query
SELECTdanSUMmembuktikan data tersimpan — total 1.155.000 adalah penjumlahan 250.000 + 175.000 + 320.000 + 410.000, dan pesanan 1004 yang dibatalkan tidak ikut dihitung.
Pipeline 41 baris ini memuat semua elemen inti ETL dalam skala kecil. Untuk membaca modul yang dipakai, rujuk dokumentasi modul csv dan referensi bahasa SQL SQLite .
Tahap Extract pada contoh di atas masih terlihat sederhana karena sumbernya satu file CSV. Pada praktiknya, ekstraksi justru paling bervariasi: sumber bisa berupa file berukuran besar, database produksi, atau API dengan batasan laju permintaan. Chapter berikutnya, Ekstraksi Data, membahas strategi seleksi kolom, sampling, dan pembacaan bertahap untuk data besar.