Dua operasi muncul hampir di setiap pipeline transformasi: menggabungkan data dari dua tabel, lalu merangkum banyak baris menjadi sedikit. Laporan “pendapatan bulanan per kota” adalah keduanya sekaligus — fakta penjualan harus diperkaya atribut kota dari dimensi, lalu diringkas dari granularitas harian menjadi bulanan. Cara menggabungkan dan merangkum menentukan biaya komputasi dan benar-tidaknya angka akhir. Chapter ini membahas strategi join untuk tabel kecil versus tabel besar, membangun agregasi bertingkat di SQLite, lalu menutup dengan jebakan klasik saat agregat diturunkan dari agregat.
Strategi Join: Tabel Kecil vs Tabel Besar #
| Aspek | Lookup dimensi kecil | Join dua tabel besar |
|---|---|---|
| Ukuran sisi lain | ratusan hingga puluhan ribu baris | kedua sisi jutaan baris |
| Di engine terdistribusi | tabel kecil disalin ke semua worker (broadcast) | data dikirim ulang antar worker (shuffle) |
| Biaya jaringan | kecil — satu kali salin | besar — sebanding ukuran data |
| Di warehouse / SQLite | JOIN biasa; engine menangani sendiri | JOIN tetap benar, tetapi butuh waktu dan index |
| Praktik terbaik | join dimensi ke tabel fakta di mana pun perlu | kumpulkan data ke satu tempat, minimalkan join berulang |
Kasus paling umum di warehouse adalah join antara fakta besar dan dimensi kecil — penjualan jutaan baris bergabung dengan tabel kota yang isinya belasan baris. Engine memenangkan kasus ini dengan mudah: dimensi yang kecil bisa dibaca penuh ke memori (atau disalin ke setiap worker di sistem terdistribusi), lalu setiap baris fakta tinggal mencocokkan kuncinya. Sebaliknya, join dua tabel besar memaksa engine memindahkan data antar node; bila pipeline kita melakukan join besar berulang kali, lebih hemat merancangnya sekali dan menyimpan hasilnya.
Sebelum menulis join, periksa juga arah penggunaannya. Bila dimensi dipakai berulang kali oleh banyak query, materialisasikan hasil join atau simpan tabel ringkas agar pekerjaan tidak diulang pada setiap eksekusi. Bila dimensi berubah sepanjang hari, pastikan snapshot yang dipakai konsisten dalam satu proses — menggabungkan fakta pagi dengan dimensi sore menghasilkan laporan yang tidak bisa direproduksi.
Contoh: Fakta Harian Menjadi Agregat Bulanan #
Data berikut adalah fakta penjualan harian selama dua bulan beserta dimensi kota. Tujuannya: satu tabel ringkas berisi total pendapatan dan jumlah hari aktif per bulan per kota, lengkap dengan atribut pulau dari dimensi.
import sqlite3
koneksi = sqlite3.connect(":memory:")
koneksi.execute("CREATE TABLE dim_kota (kota TEXT PRIMARY KEY, pulau TEXT)")
koneksi.executemany("INSERT INTO dim_kota VALUES (?, ?)", [
("Jakarta", "Jawa"),
("Bandung", "Jawa"),
("Surabaya", "Jawa"),
])
koneksi.execute(
"CREATE TABLE fakta_penjualan_harian (tanggal TEXT, kota TEXT, total INTEGER)"
)
koneksi.executemany("INSERT INTO fakta_penjualan_harian VALUES (?, ?, ?)", [
("2026-09-01", "Jakarta", 1250000),
("2026-09-02", "Bandung", 640000),
("2026-09-03", "Surabaya", 530000),
("2026-09-04", "Jakarta", 980000),
("2026-09-05", "Bandung", 715000),
("2026-09-06", "Jakarta", 1420000),
("2026-09-07", "Surabaya", 1110000),
("2026-10-01", "Jakarta", 1180000),
("2026-10-02", "Bandung", 830000),
("2026-10-03", "Surabaya", 470000),
("2026-10-04", "Jakarta", 1360000),
("2026-10-05", "Bandung", 690000),
("2026-10-06", "Surabaya", 580000),
("2026-10-07", "Jakarta", 1050000),
])
# Join (lookup dimensi kecil) + agregasi bertingkat: harian -> bulanan per kota
koneksi.execute("""
CREATE TABLE agregat_bulanan AS
SELECT
substr(f.tanggal, 1, 7) AS bulan,
d.kota,
d.pulau,
SUM(f.total) AS total_bulanan,
COUNT(*) AS hari_aktif
FROM fakta_penjualan_harian AS f
JOIN dim_kota AS d ON f.kota = d.kota
GROUP BY substr(f.tanggal, 1, 7), d.kota, d.pulau
ORDER BY bulan, d.kota
""")
for baris in koneksi.execute("SELECT * FROM agregat_bulanan"):
print(baris)
koneksi.close()Output yang Dihasilkan:
('2026-09', 'Bandung', 'Jawa', 1355000, 2)
('2026-09', 'Jakarta', 'Jawa', 3650000, 3)
('2026-09', 'Surabaya', 'Jawa', 1640000, 2)
('2026-10', 'Bandung', 'Jawa', 1520000, 2)
('2026-10', 'Jakarta', 'Jawa', 3590000, 3)
('2026-10', 'Surabaya', 'Jawa', 1050000, 2)Mari kita telaah bagian-bagian kunci di atas:
- Baris 4-9 (dimensi kecil):
dim_kotaberisi tiga baris — inilah sisi lookup: dimuat sekali, dipakai untuk setiap baris fakta. - Baris 13-28 (fakta harian): empat belas baris, dua baris per kota per bulan ke atas; pada data nyata, tabel seperti ini berisi jutaan baris.
- Baris 34 (ekstraksi bulan):
substr(f.tanggal, 1, 7)memotong"2026-09-01"menjadi"2026-09"— kunci agregasi bulanan lahir dari kolom tanggal tanpa tabel kalender tambahan. - Baris 40 (join):
JOIN dim_kota ON f.kota = d.kotamenempelkan atribut pulau ke setiap baris fakta sebelum diringkas. - Baris 41 (kelompok):
GROUP BYmemakai kunci bulan + kota + pulau;SUMmenjumlahkan total,COUNT(*)menghitung hari aktif — Jakarta September tampil 3.650.000 dari 1.250.000 + 980.000 + 1.420.000. - Baris 42 (urutan hasil):
ORDER BY bulan, d.kotamembuat tabel ringkasan terbaca kronologis — bulan September lengkap lebih dulu, baru Oktober.
Verifikasi silang dua angka output: total Jakarta September (3.650.000) dibagi hari aktif (3) memberi rata-rata harian 1.216.667 — konsisten dengan tiga baris fakta sumbernya.
Jebakan Agregat dari Agregat #
Ketika laporan bulanan sudah tersimpan, godaan berikutnya adalah membangun laporan tahunan dari tabel bulanan. Untuk SUM operasi itu aman: jumlah tahunan memang penjumlahan dua belas jumlah bulanan. Bahaya muncul pada AVG — rata-rata tahunan bukan rata-rata dari dua belas rata-rata bulanan, kecuali setiap bulan berisi jumlah hari yang sama persis.
Kolom hari_aktif pada contoh bukan pelengkap. Kolom tersebut menyimpan pembagi untuk rata-rata di masa depan: setiap kali laporan baru meminta rata-rata pendapatan harian, SUM(total_bulanan) / SUM(hari_aktif) menghasilkan angka yang benar tanpa membuka ulang tabel fakta.
Agregasi bertingkat yang aman menyimpan dua kolom sejak awal: nilai terjumlah (SUM) dan pembagi (COUNT). Dari pasangan tersebut, rata-rata pada granularitas mana pun bisa dihitung ulang dengan benar. Pola GROUP BY dan fungsi agregasi yang dipakai di sini dibahas lebih rinci pada chapter GROUP BY dan HAVING di section Dasar SQL, sedangkan rujukan sintaks lengkap ada di referensi bahasa SQLite
.
Satu masalah belum tersentuh sepanjang section ini: apa yang terjadi bila join dan agregat tersebut menerima baris yang sama dua kali? Duplikat seperti itu tidak terlihat dari query — jumlahnya tetap benar secara sintaks, tetapi salah secara bisnis. Chapter berikutnya, Deduplikasi Data, membahas dari mana duplikat datang dan dua cara membersihkannya.