Delete + Insert: Replacing the Run's Partition
Write a batch load that owns exactly one date partition, driven by a run parameter instead of the wall clock, and prove it survives a rerun.
The property, and the sentence that carries it
A load is idempotent when running it twice leaves the table exactly as running it once did. That single property is what makes retries, backfills, and late-data reconciliation safe, and it is why every distributed-systems answer in a data interview eventually lands on the same sentence:
True exactly-once delivery is impossible. At-least-once delivery plus an idempotent sink equals effectively exactly-once.
You cannot stop a queue, a scheduler, or an on-call engineer from delivering the same batch twice. You can make the sink not care. There are exactly three implementations, and this module grades all three. The first one is the one you reach for first.
Implementation 1: delete the partition, then insert it
The simplest idempotent pattern that works on every engine ever built:
DELETEevery row of the target belonging to this run's partition.INSERT ... SELECTthat partition fresh from the batch.
The run owns the partition. Whatever was there before is gone, whether it was correct, half-written, or stale. Two runs of the same script land the same rows because the delete wipes the first run's output before the second one writes.
- run_configrun_date = 2026-03-14, injected
- DELETE dtwipe the run's partition
- INSERT dtwrite it fresh from the batch
- fct_ordersthe partition is owned, not appended to
You have written this mechanic before. In sql-l5-incremental-watermark-backfill you deleted a load_date range and reinserted it, and the partition date was typed into the SQL as a literal. That is the part this lesson replaces.
The date is a parameter, never a clock
The single most damaging anti-pattern in pipeline code is reading the wall clock. datetime.now(), CURRENT_DATE, or a hardcoded 'today' inside pipeline logic means:
- A rerun of yesterday's failed job processes today instead, silently.
- A backfill of March cannot work at all, because every run computes June.
- The same code produces a different answer depending on when it happened to execute.
The fix is that the run's date is an input. The orchestrator computes the data interval for the run and injects it, and the job reads it from there. In this lesson that injection is a run_config(pipeline, run_date) row, and your script reads run_date out of it. In Airflow it is the logical date passed into the task. In a warehouse job it is a bound parameter. The shape is always the same: the run is responsible for a time range, and something outside the job tells it which one.
That is also what makes the load re-runnable at all. A run bound to an immutable data interval produces the same output every time you run it, which is the precondition idempotency is built on.
Nothing can catch this failure for you by looking at output. A script with the date typed into it returns the correct partition on the day it was written, and on every rerun of that same day, which is why the exercises below cannot detect a literal either. The bug only surfaces the first time the run's date is not the date you typed, and by then it is a backfill that silently wrote the wrong day. Reading the parameter is a habit, not a test you can pass.
Atomicity, and where the boundaries go
The delete and the insert are one logical unit. If the delete lands and the insert fails, the partition is empty, which is worse than stale. Warehouses give you a transaction or an atomic INSERT OVERWRITE for this. The task boundary rule that goes with it is to keep extract, transform, and load in separate, independently re-runnable tasks, so a failure in one does not force you to redo the expensive ones.
Common mistake: deleting by a filter that is wider than what you insert. DELETE FROM fct_orders WHERE dt >= '2026-03-14' followed by an insert of one day quietly destroys every later partition. The delete predicate and the insert predicate must describe the same partition, which is why both should read the same parameter.
Interview nuance: when an interviewer says "make this backfill safe to rerun," lead with the sentence, then name delete-plus-insert as the implementation you reach for first, then say the date comes from the orchestrator and never from the clock. The delete itself is table stakes. The parameter is the detail that reads as production experience.
On a real platform this differs. Here you write a
DELETEand anINSERT ... SELECTagainst SQLite. Hive, Spark SQL, and Databricks spell the same thingINSERT OVERWRITEon a partition, Delta Lake spells itreplaceWhere, BigQuery replaces a partition with a WRITE_TRUNCATE load or query against atable$YYYYMMDDdecorator (it has noINSERT OVERWRITEstatement, so the alternative there isMERGE), Oracle can exchange a prepared partition in as a metadata operation, and Iceberg does it as a snapshot commit. Snowflake is the odd one out: its micro-partitions are automatic and not addressable, so there is nothing to swap at partition level and you replace a whole table withCREATE OR REPLACE TABLE ... SWAP WITHinstead. All of them are delete-plus-insert with the atomicity handled for you. Airflow supplies therun_datethisrun_configrow stands in for, as the run's logical date.
CREATE TABLE blind_append (order_id TEXT, dt TEXT, amount_usd REAL);
INSERT INTO blind_append (order_id, dt, amount_usd) VALUES
('O-3141', '2026-03-14', 84.50), ('O-3142', '2026-03-14', 129.95), ('O-3143', '2026-03-14', 46.20),
('O-3141', '2026-03-14', 84.50), ('O-3142', '2026-03-14', 129.95), ('O-3143', '2026-03-14', 46.20);
CREATE TABLE replaced_partition (order_id TEXT, dt TEXT, amount_usd REAL);
INSERT INTO replaced_partition (order_id, dt, amount_usd) VALUES
('O-3141', '2026-03-14', 84.50), ('O-3142', '2026-03-14', 129.95), ('O-3143', '2026-03-14', 46.20);-- The same three-row batch, loaded twice by each loader. Only one of them is idempotent.
SELECT 'blind append (INSERT only)' AS loader,
COUNT(*) AS rows_after_two_runs,
ROUND(SUM(amount_usd), 2) AS revenue_usd
FROM blind_append
UNION ALL
SELECT 'delete + insert the partition',
COUNT(*),
ROUND(SUM(amount_usd), 2)
FROM replaced_partition;Apply
Your turn
The task this lesson builds to.
Write a script that loads batch_orders into fct_orders so the run's partition is fully replaced and rerunning the script changes nothing, over batch_orders(order_id, dt, customer_id, amount_usd), fct_orders(order_id, dt, customer_id, amount_usd), and run_config(pipeline, run_date).
Read the partition date from run_config where pipeline = 'orders_daily' rather than typing '2026-03-14'. The grader reseeds the same date on every run, so it cannot see a literal in your script, but in production a hardcoded date is exactly the wall-clock leak this lesson is about: the rerun of a failed job and the backfill of March both silently process the wrong day. Delete that partition from fct_orders, then insert it fresh from batch_orders. This is a backfill, so fct_orders already holds a later 2026-03-15 partition as well as 2026-03-13, and both must come out untouched.
3 hints and 4 automated checks are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, plus 2 bonus drills.
Write a script that rebuilds the daily rollup fct_daily_revenue(dt, order_count, revenue_usd) for the run's date only, leaving every other date alone and staying safe to rerun, over fct_orders(order_id, dt, customer_id, amount_usd) and run_config(pipeline, run_date).
order_count is the number of orders in that date's partition and revenue_usd is their summed amount_usd rounded to 2 decimals. The stale 2026-03-14 row currently in fct_daily_revenue must be replaced, not added to, and the 2026-03-12, 2026-03-13, and 2026-03-15 rows must survive exactly as they are. Rebuilding the whole rollup is not an option: 2026-03-12 is older than fct_orders retention, so a full recompute would quietly delete a day nothing can recreate.
2 hints and 3 automated checks are waiting in the workspace.