Step 4: store and model
Keep three layers. Raw keeps the events as they came.
Cleaned has one row for each order, with the right types. Serving has the tables people query.
Write Parquet files. Use a table format such as Delta or Iceberg, because it supports MERGE. At 200 orders a day the table is small, so you do not need a partition yet.
Add one on order_date when it grows.
At 200 orders a day, most runs see no new order. A run with no events writes nothing. A daily job compacts the small files into bigger ones.
The model has a fact table, fact_orders, with one row for each order. A dim_customer table holds the customer facts.
Use SCD2 on it. SCD2 means slowly changing dimension, type 2: each change adds a new row. So an old order keeps the customer facts it had that day.
Step 5: transform and load
Each run keeps the newest event for each order. Then it writes with MERGE on order_id. warehouse.orders is the cleaned table. A retry gives the same result.
This query is one run. It uses the three MERGE rules from the CDC chapter.
The pos guard is the check e.pos > t.last_pos. It skips an event that the table already has:
One run, with a retried eventworked example
MERGE INTO warehouse.orders AS t
USING latest AS e
ON t.order_id = e.order_id
WHEN MATCHED AND e.op = 'd' AND e.pos > t.last_pos THEN
DELETE
WHEN MATCHED AND e.pos > t.last_pos THEN
UPDATE SET status = e.status, amount = e.amount, last_pos = e.pos
WHEN NOT MATCHED AND e.op <> 'd' THEN
INSERT (order_id, customer_id, status, amount, last_pos)
VALUES (e.order_id, e.customer_id, e.status, e.amount, e.pos);
warehouse.orders (after)
| order_id | customer_id | status | amount | last_pos |
|---|
| 110 | 2 | paid | 30.00 | 504 |
| 111 | 5 | paid | 9.00 | 601 |
Event 504 matched order 110, but 504 is not greater than 504, so nothing changed. Event 601 was new, so order 111 was inserted. An event with op d would delete its row.