Lesson 7 of 8 · 50 min
Warehouse facts, dims & events
Star schema, additive vs semi-additive measures, SCD2 point-in-time, funnels, conformed dims, partitioning and incremental loads.
Lesson 7 · Warehouse modeling
Facts, dims, SCD, events - analytics modeling that interviews probe
OLTP instincts fail in the warehouse
Facts and dimensions (star schema)
1-- Fact: order_items grain2fact_order_items (3 order_item_id,4 order_id,5 date_key, -- FK date dim6 customer_key, -- FK customer dim (surrogate)7 product_key,8 qty,9 gross_cents,10 discount_cents,11 net_cents12)1314dim_customer (customer_key SK, customer_id NK, country, segment, ...)15dim_product (product_key SK, sku NK, category, ...)16dim_date (date_key, date, week, month, fiscal_year, is_holiday)1718-- Grain sentence: one row per order line per order.Key idea
Additive, semi-additive, non-additive measures
1-- Semi-additive: daily account balance snapshot2fact_account_daily_balance (3 date_key, account_key, balance_cents4)5-- Correct: SUM(balance) for a day across accounts6-- Wrong: SUM(balance) across days for 'total money'78-- Use last snapshot in range or average balance rules as product definesSlowly changing dimensions (SCD)
1dim_customer_scd2 (2 customer_key BIGSERIAL PK, -- surrogate per version3 customer_id BIGINT NOT NULL, -- natural key4 country TEXT,5 segment TEXT,6 valid_from TIMESTAMPTZ NOT NULL,7 valid_to TIMESTAMPTZ, -- null or '9999' if current8 is_current BOOLEAN NOT NULL9);1011-- Fact points at customer_key version true at event time12-- Point-in-time join:13SELECT f.*, d.segment14FROM fact_order_items f15JOIN dim_customer_scd2 d16 ON d.customer_id = f.customer_id17 AND f.ordered_at >= d.valid_from18 AND (d.valid_to IS NULL OR f.ordered_at < d.valid_to);Common mistake
Always join facts to is_current = true dimension rows.
Event modeling and funnels
1-- Simplified funnel: view → add_cart → purchase within 7 days2WITH views AS (3 SELECT user_id, MIN(ts) AS view_ts4 FROM events WHERE event_name = 'view_product'5 GROUP BY 16),7carts AS (8 SELECT e.user_id, MIN(e.ts) AS cart_ts9 FROM events e10 JOIN views v ON v.user_id = e.user_id11 WHERE e.event_name = 'add_cart'12 AND e.ts >= v.view_ts13 AND e.ts < v.view_ts + INTERVAL '7 days'14 GROUP BY 115),16purchases AS (17 SELECT e.user_id, MIN(e.ts) AS purchase_ts18 FROM events e19 JOIN carts c ON c.user_id = e.user_id20 WHERE e.event_name = 'purchase'21 AND e.ts >= c.cart_ts22 AND e.ts < c.cart_ts + INTERVAL '7 days'23 GROUP BY 124)25SELECT26 (SELECT COUNT(*) FROM views) AS n_view,27 (SELECT COUNT(*) FROM carts) AS n_cart,28 (SELECT COUNT(*) FROM purchases) AS n_purchase;Wide facts vs multiple facts; conformed dims
Partitioning, clustering, incremental loads
1-- Idempotent daily load sketch (conceptual)2DELETE FROM fact_order_items WHERE date_key = :load_date;3INSERT INTO fact_order_items4SELECT ... FROM staging_orders WHERE order_date = :load_date;56-- Or MERGE on natural keys for slowly changing facts7MERGE INTO fact_x t8USING staging_x s ON t.nk = s.nk9WHEN MATCHED THEN UPDATE SET ...10WHEN NOT MATCHED THEN INSERT ...;Common mistake
Warehouse tables should always mirror OLTP 3NF for consistency.
Interview answers - warehouse
- 01"What is fact grain?" → one sentence: one row per X; all measures must make sense at X.
- 02"Star vs snowflake?" → star denormalized dims for BI speed; snowflake normalizes dims further.
- 03"SCD2 when?" → need attribute history for point-in-time analytics (segment, plan, country).
- 04"is_current join risk?" → rewrites history; use version key or valid_from/to.
- 05"Semi-additive example?" → balances, headcount snapshots - do not sum across time blindly.
- 06"Conformed dimension?" → shared dim definition across facts (date, customer).
- 07"Why separate event raw + marts?" → raw immutable; marts serve stable metrics with tests.
- 08"Late arriving fact?" → reload partition / merge; watch SCD timing.
- 09"Allocation problem?" → header fee to lines needs explicit rule (equal, revenue weight).
- 10"dbt tests?" → unique grain, not_null keys, accepted values, relationship tests.
Checkpoint
fact_order_items is line grain; shipping_fee lives once per order. Analyst SUM(shipping_fee) after joining fee to every line - result?
Checkpoint
Customer segment changes from free→pro in June. You need May revenue by segment at that time. SCD approach?
Checkpoint
Account balances snapshotted daily. Stakeholder asks for SUM(balance) across all days in Q1 for 'total deposits'. Issue?
Checkpoint
Why prefer raw events + derived marts over only curated funnel tables?
Checkpoint
Conformed dimension means?
Can you declare fact grain, pick SCD2 when history matters, and call out semi-additive measures in a design interview?
Takeaways
- Always state fact grain in one sentence.
- Star schema: facts + dims; conform dims across facts.
- SCD2 + point-in-time joins preserve history.
- Semi-additive measures: do not sum across time blindly.
- Raw events + marts; incremental idempotent loads.
Next: capstone - live SQL + modeling mock with a full rubric.
Sources
Free to read · better with Enzo
Learn it with Enzo
Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.