Data Modeling (Star Schema) — Data Engineering

Star Schema & Dimensional Modeling Star schema adalah teknik data modeling yang paling umum di data warehouse. Dinamakan "star" karena bentuknya: satu fact…

Star schema adalah cara menyusun tabel di warehouse supaya pertanyaan bisnis gampang dijawab: satu fact table di tengah yang mencatat kejadian beserta angkanya, dikelilingi dimension table yang menjelaskan siapa, apa, dan kapan. Bayangin gulungan struk kasir. Tiap baris struk cuma menulis kode barang, kode member, tanggal, jumlah, dan harga. Nama barang dan penulisnya ada di katalog, nama dan kota member ada di daftar member, dan "hari apa, minggu ke berapa" ada di kalender. Gulungan struk itu fact table; katalog, daftar member, dan kalender itu dimension table. Kalau digambar, fact di tengah dengan dimensi di sekelilingnya, bentuknya seperti bintang.

🧪 Tebakan cepat: Sabtu 10 Oktober, harga Bumi Manusia turun dari 125.000 jadi 119.000. Tiap malam pipeline membangun ulang fact_penjualan, dan harganya diambil dari dim_buku. Omzet minggu 28 September tadinya Rp 1.370.000. Setelah pipeline jalan lagi, jadi berapa? Tebak dulu, jawabannya ada di bagian "Slowly changing dimension: harga yang berubah".

Grain dulu: satu baris itu apa?

Sebelum membuat tabel apa pun, tentukan grain: satu baris fact table mewakili apa. Untuk Toko Buku, grain-nya satu buku dalam satu pesanan, alias satu baris pesanan (order line). Datanya yang sudah bersih: 18 baris, 21 buku, Rp 2.214.000.

Kenapa gak satu baris per pesanan? Pesanan 2 berisi Bumi Manusia dan Cantik Itu Luka. Kalau dilipat jadi satu baris, judulnya hilang, dan pertanyaan pemilik ("buku apa paling laku per kota per minggu?") gak bisa dijawab lagi. Pilih grain paling halus yang ada di sumber: yang halus bisa dijumlah jadi kasar, sebaliknya gak bisa.

Fact table: kunci dan angka

Fact table berisi dua macam kolom: kunci ke tiap dimensi, dan measure, angka yang bisa dijumlah.

CREATE TABLE fact_penjualan (
  order_id INT,        -- nomor pesanan, gak punya tabel dimensi sendiri
  tanggal_key INT,     -- ke dim_tanggal, misalnya 20261001
  buku_key INT,        -- ke dim_buku
  pelanggan_key INT,   -- ke dim_pelanggan
  qty INT,             -- measure: jumlah buku
  harga INT,           -- measure: harga per buku saat itu
  subtotal INT         -- measure: qty * harga
);

Diisi dari tabel aplikasi, sambil menerapkan aturan bersih dari pelajaran sebelumnya:

INSERT INTO fact_penjualan
SELECT i.order_id,
       CAST(strftime('%Y%m%d', o.created_at) AS INT),
       b.buku_key,
       p.pelanggan_key,
       i.qty,
       b.harga,
       i.qty * b.harga
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id, book_id ORDER BY id) AS ke
      FROM order_items) i
JOIN orders o ON o.id = i.order_id
JOIN dim_pelanggan p ON p.customer_id = o.customer_id
JOIN dim_buku b ON b.book_id = i.book_id
               AND date(o.created_at) >= b.valid_from
               AND date(o.created_at) < b.valid_to
WHERE i.ke = 1;
order_id tanggal_key buku_key pelanggan_key qty harga subtotal
1 20260928 1 1 1 89000 89000
2 20260928 2 2 1 125000 125000
2 20260928 3 2 1 110000 110000
6 20261001 2 1 2 125000 250000

Isinya cuma kunci dan angka. Itu sebabnya fact table yang menyimpan metrik yang bisa diagregasi (SUM(qty), SUM(subtotal)), sedangkan teks penjelasnya ada di dimensi.

Dimension table: konteks yang dibaca manusia

Tiga dimensi Toko Buku, satu baris per "benda":

Tabel Satu baris = Kolom
dim_buku satu versi buku buku_key, book_id, judul, penulis, harga, valid_from, valid_to, is_current
dim_tanggal satu hari tanggal_key (20261001), tanggal, hari (Kamis), minggu (Senin awal minggunya), akhir_pekan
dim_pelanggan satu pelanggan pelanggan_key, customer_id, nama, kota (Maya: Tidak diketahui)

Pertanyaan pemilik di star schema

WITH per_judul AS (
  SELECT t.minggu, p.kota, b.judul, SUM(f.qty) AS terjual
  FROM fact_penjualan f
  JOIN dim_tanggal t ON t.tanggal_key = f.tanggal_key
  JOIN dim_pelanggan p ON p.pelanggan_key = f.pelanggan_key
  JOIN dim_buku b ON b.buku_key = f.buku_key
  GROUP BY t.minggu, p.kota, b.judul
),
peringkat AS (
  SELECT *, RANK() OVER (PARTITION BY minggu, kota ORDER BY terjual DESC) AS posisi
  FROM per_judul
)
SELECT minggu, kota, judul, terjual
FROM peringkat
WHERE posisi = 1
ORDER BY minggu, kota, judul;

Hasilnya 9 baris yang sama dengan pelajaran SQL: Jakarta minggu 28 September Laskar Pelangi 3, Surabaya minggu 5 Oktober seri. Bedanya, pembersihan, kalender, dan nama kota sudah dikerjakan sekali waktu tabelnya dibangun. Query-nya tinggal fact plus satu JOIN per dimensi.

Star atau snowflake

Kalau dimensinya dinormalisasi lagi, penulis pindah ke dim_penulis dan kota ke tabel kota sendiri, bentuknya bercabang seperti kepingan salju: snowflake schema. Pertanyaan "buku terjual per penulis per kota" butuh 2 JOIN di star schema (dim_buku, dim_pelanggan) dan 4 JOIN di snowflake.

Di database aplikasi (OLTP), normalisasi penuh (3NF) itu benar: satu fakta disimpan sekali, jadi update gak bikin data saling bertentangan. Warehouse beda kebutuhannya: dibaca jauh lebih sering daripada diubah, dan yang mengubah cuma pipeline. Keuntungan star schema dibanding 3NF: query lebih sederhana dan JOIN lebih cepat karena dimensinya didenormalisasi. Harganya, teks seperti "Pramoedya Ananta Toer" ditulis berulang. Warehouse menyimpan data per kolom dan memampatkan nilai yang berulang, jadi biayanya kecil.

Slowly changing dimension: harga yang berubah

Dimensi jarang berubah, tapi bisa. Sabtu 10 Oktober, harga Bumi Manusia turun jadi 119.000, dan pagi itu Siti membeli satu (pesanan 15, pukul 10:15:30). Cara warehouse menangani perubahan ini disebut SCD (slowly changing dimension).

SCD tipe 1: timpa.

UPDATE dim_buku SET harga = 119000 WHERE book_id = 2;

Malamnya pipeline membangun ulang fact, dan semua baris Bumi Manusia, termasuk pesanan 28 September, sekarang 119.000. Omzet minggu 28 September berubah sendiri dari Rp 1.370.000 jadi Rp 1.346.000 (4 buku × 6.000 hilang). Laporan minggu lalu berubah padahal gak ada yang terjadi minggu lalu. Itu jawaban tebakannya, untuk tipe 1. Tipe 1 cocok untuk membetulkan salah ketik, bukan untuk perubahan yang punya sejarah.

SCD tipe 2: tambah baris versi baru.

UPDATE dim_buku
SET valid_to = '2026-10-10', is_current = FALSE
WHERE book_id = 2 AND is_current;

INSERT INTO dim_buku
VALUES (5, 2, 'Bumi Manusia', 'Pramoedya Ananta Toer', 119000, '2026-10-10', '9999-12-31', TRUE);
buku_key book_id harga valid_from valid_to is_current
2 2 125000 2026-09-01 2026-10-10 0
5 2 119000 2026-10-10 9999-12-31 1

Tipe 3 (simpan nilai sebelumnya di kolom tambahan, misalnya harga_sebelumnya) cuma ingat satu perubahan terakhir, jadi jarang dipakai.

⚠️ Jebakan yang sering ketemu

TL;DR: Star schema = satu fact table di tengah (kunci ke dimensi + measure yang bisa dijumlah: qty, harga, subtotal) dan dimension table di sekelilingnya (teks penjelas: dim_buku, dim_tanggal, dim_pelanggan). Tentukan grain dulu; Toko Buku: satu buku dalam satu pesanan, 18 baris. Dimensi punya surrogate key sendiri (buku_key) terpisah dari natural key (book_id). Dimensi didenormalisasi (penulis ikut di dim_buku), jadi query cuma butuh satu JOIN per dimensi: lebih sederhana dan lebih cepat daripada snowflake atau 3NF. Kalau dimensi berubah: SCD tipe 1 menimpa dan menulis ulang sejarah (minggu 28 September jadi Rp 1.346.000), SCD tipe 2 menambah versi dengan valid_from, valid_to, is_current sehingga pesanan lama tetap memakai harga lamanya (tetap Rp 1.370.000).

Yang akan kamu pelajari