Why loads need careful SQL
Three earlier chapters gave you the ideas. They are “Loading & Write Patterns”, “Idempotency, Retries & Exactly-Once” and “Incremental Processing & Backfills”.
This chapter gives you the SQL that does the work. It is PostgreSQL unless a note says otherwise.
A load changes a table that other people already use. So the SQL has one job: leave the table correct, even if the load runs twice.
Most examples use two tables. The table dw_orders is the warehouse copy of the shop’s orders. The table orders_stage is a new batch of changes.
Here is the warehouse table now, and the batch that reached it on 2026-01-27:
| order_id | customer_id | status | amount | order_date | updated_at |
|---|---|---|---|---|---|
| 101 | 1 | paid | 40.00 | 2026-01-03 | 2026-01-03 09:00 |
| 102 | 1 | paid | 25.00 | 2026-01-10 | 2026-01-10 09:00 |
| 103 | 2 | refunded | 60.00 | 2026-01-11 | 2026-01-11 09:00 |
| 104 | 3 | paid | 15.50 | 2026-01-15 | 2026-01-15 09:00 |
| 105 | 3 | pending | NULL | 2026-01-20 | 2026-01-20 09:00 |
| 106 | 4 | paid | 80.00 | 2026-01-21 | 2026-01-21 09:00 |
The updated_at values are made up. Each row was last changed at 09:00 on its order date.
SELECT *
FROM orders_stage;| order_id | customer_id | status | amount | order_date | updated_at |
|---|---|---|---|---|---|
| 105 | 3 | paid | 22.00 | 2026-01-20 | 2026-01-27 08:00 |
| 105 | 3 | pending | NULL | 2026-01-20 | 2026-01-20 09:00 |
| 130 | 5 | paid | 30.00 | 2026-01-26 | 2026-01-26 10:00 |
| 106 | 4 | paid | 80.00 | 2026-01-21 | 2026-01-21 09:00 |
| NULL | 2 | paid | 12.00 | 2026-01-26 | 2026-01-26 11:00 |
All of this batch is made up for the chapter. In the story, order 105 was paid later, at 22.00. Order 130 and the last row are new.
The ten patterns come in this order:
- Pick a write strategy: append, full refresh, delete and insert, or upsert.
- Clean the batch in a staging table, and keep one row for each key.
- Upsert with INSERT … ON CONFLICT.
- Merge with MERGE, which needs PostgreSQL 15.
- Handle deleted rows.
- Write set-based statements and wrap them in a transaction.
- Read only new rows with a watermark and a lookback.
- Build a date spine with generate_series.
- Backfill a date range, then load a slowly changing dimension (SCD2).
- Reconcile the source and the target.
