06.5 Delta Lake MERGE, UPDATE & DELETE
Delta Lake is not just an append-only file format. Its transaction protocol lets you change table state atomically through MERGE, UPDATE and DELETE.
The important architectural distinction is:
UPDATE and DELETE change existing table state; MERGE combines matching, insertion and deletion logic into one transactional operation.
1. The mental model
Think of a Delta mutation as:
Source/change intent | v Match target rows | +----+----+ | | MATCH NOT MATCHED | | UPDATE INSERT | optional DELETE | v Delta transaction | v New table snapshot
A mutation does not mean "edit a Parquet row in place."
Delta records a new table state through the transaction log. The physical implementation can involve reading existing files, writing replacement files, and committing adds/removes atomically.
2. UPDATE
Use UPDATE when the target rows are already known and you need to change their values.
SQL
UPDATE main.sales.orders
SET status = 'CLOSED'
WHERE order_date < current_date() - INTERVAL '90' DAY
AND status = 'OPEN';
This is useful for:
- correcting attributes
- standardizing values
- applying business rules
- status transitions
- controlled backfills
Databricks supports SQL UPDATE for Delta tables. See Databricks — UPDATE.
3. UPDATE is not a row-level in-place edit
Conceptually:
Before
file_001.parquet file_002.parquet
UPDATE
Read affected data | Write new file(s) | Commit transaction | Old files become inactive
The transaction log determines which files belong to the current table version.
This is why an UPDATE can be much more expensive than changing a single database row.
4. DELETE
DELETE removes rows matching a predicate.
DELETE FROM main.sales.orders
WHERE customer_id = 10042;
Typical use cases:
- GDPR/privacy deletion
- invalid-record removal
- source-system corrections
- retention policies
- business-rule cleanup
See Databricks — DELETE.
Important
A DELETE does not mean that the underlying Parquet bytes immediately disappear from object storage.
The Delta transaction changes the active table snapshot. Physical file cleanup is a separate lifecycle operation, normally involving VACUUM.
5. MERGE — the enterprise workhorse
MERGE is used when incoming records must be compared with an existing target.
Typical pattern:
MERGE INTO target AS t
USING source AS s
ON t.business_key = s.business_key
WHEN MATCHED THEN
UPDATE SET *
WHEN NOT MATCHED THEN
INSERT *;
This gives you an atomic combination of:
- update existing rows
- insert new rows
- optionally delete matched rows
- optionally modify rows not present in the source
Databricks documents MERGE as the primary Delta operation for upsert-style workloads. Databricks — MERGE
6. MERGE clause model
Current Databricks SQL supports three major branches:
WHEN MATCHED | +--> UPDATE | +--> DELETE
WHEN NOT MATCHED | +--> INSERT
WHEN NOT MATCHED BY SOURCE | +--> UPDATE | +--> DELETE
The third branch is particularly useful when synchronizing a target with a source that represents the authoritative state.
The current SQL syntax is documented in MERGE INTO.
7. WHEN MATCHED
A matched source row has a corresponding target row according to the ON condition.
Update
WHEN MATCHED THEN
UPDATE SET
t.amount = s.amount,
t.status = s.status,
t.updated_at = current_timestamp()
Conditional update
WHEN MATCHED
AND s.updated_at > t.updated_at
THEN UPDATE SET *
This is a powerful pattern for late-arriving or replayed data.
8. Multiple WHEN MATCHED clauses
You can have multiple matched clauses.
Example:
WHEN MATCHED AND s.operation = 'DELETE'
THEN DELETE
WHEN MATCHED AND s.operation = 'UPDATE'
THEN UPDATE SET *
WHEN MATCHED
THEN UPDATE SET *
Order matters.
Earlier conditional clauses are evaluated before later clauses. Databricks requires conditions on all but the final clause of a given type.
See MERGE semantics.
9. WHEN NOT MATCHED
This branch handles source rows that have no target match.
WHEN NOT MATCHED THEN
INSERT (
order_id,
customer_id,
amount,
status
)
VALUES (
s.order_id,
s.customer_id,
s.amount,
s.status
)
Use explicit column mappings when the contract is sensitive.
Use INSERT * when the source and target are intentionally aligned.
10. WHEN NOT MATCHED BY SOURCE
This clause operates on target rows that have no matching source row.
Example:
WHEN NOT MATCHED BY SOURCE
AND t.is_active = true
THEN UPDATE SET
t.is_active = false,
t.closed_at = current_timestamp()
Or:
WHEN NOT MATCHED BY SOURCE
AND t.snapshot_date = current_date()
THEN DELETE
This is useful for synchronization patterns, but it can be dangerous.
If the source represents only a partial extract, treating every missing source row as a deletion can incorrectly remove valid target records.
11. The most important MERGE rule: source uniqueness
A MERGE can fail when multiple source rows match the same target row and the operation attempts to modify that target row.
Example:
Target
order_id = 1001
Source
order_id = 1001 order_id = 1001
Which source record should win?
There is no unambiguous answer.
Current Databricks behavior also considers matched-clause conditions when determining duplicate matches in newer runtimes; older runtimes used only the ON clause. Always verify the runtime behavior in production. Databricks — MERGE
Production rule
Before MERGE:
SELECT business_key, COUNT(*) AS cnt
FROM source
GROUP BY business_key
HAVING COUNT(*) > 1;
Then decide how duplicates should be resolved.
12. Deterministic source deduplication
For CDC or event streams, a common pattern is to select the latest event per business key.
WITH ranked AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY business_key
ORDER BY event_timestamp DESC, event_id DESC
) AS rn
FROM source_changes
)
SELECT *
FROM ranked
WHERE rn = 1;
This converts:
many source events | v deterministic winner | v MERGE
The tie-breaker must itself be deterministic.
13. UPDATE versus MERGE
| Requirement | Preferred operation |
|---|---|
| Change known target rows | UPDATE |
| Remove known target rows | DELETE |
| Insert-only load | INSERT |
| Upsert | MERGE |
| Synchronize authoritative source | MERGE |
| CDC with I/U/D | MERGE |
| Complex source-to-target matching | MERGE |
Do not automatically use MERGE for every mutation.
If a simple UPDATE can solve the problem, it may be clearer and cheaper.
14. MERGE and CDC
A CDC feed commonly contains:
INSERT UPDATE DELETE
A Delta target can apply these events transactionally.
Conceptually:
CDC event | +---- I ---> INSERT | +---- U ---> UPDATE | +---- D ---> DELETE
Example:
MERGE INTO silver.customer AS t
USING deduplicated_cdc AS s
ON t.customer_id = s.customer_id
WHEN MATCHED AND s.operation = 'DELETE'
THEN DELETE
WHEN MATCHED AND s.operation IN ('UPDATE', 'UPSERT')
THEN UPDATE SET *
WHEN NOT MATCHED AND s.operation <> 'DELETE'
THEN INSERT *;
For production CDC pipelines, also consider Lakeflow's native CDC capabilities where they fit the architecture. Databricks — MERGE
15. SCD Type 1
SCD Type 1 keeps the latest value.
Example:
Customer 42
Old: city = Bengaluru
New: city = Hyderabad
MERGE:
MERGE INTO dim_customer AS t
USING customer_updates AS s
ON t.customer_id = s.customer_id
WHEN MATCHED THEN
UPDATE SET *
WHEN NOT MATCHED THEN
INSERT *;
Result:
customer_id = 42 city = Hyderabad
The old value is not represented as a current dimension row.
16. SCD Type 2
SCD Type 2 preserves history.
Typical target:
customer_id city effective_from effective_to is_current
The architecture becomes:
Current dimension | Detect change | +---+---+ | | Close Insert old new row row
MERGE can participate in SCD2 implementations, but SCD2 is more than a single UPDATE statement. It requires deterministic change detection, effective dating, and careful handling of multiple versions.
For managed CDC pipelines, Databricks provides native SCD Type 1 and Type 2 patterns through Lakeflow pipelines. Databricks — MERGE
17. MERGE with schema evolution
Modern Databricks supports:
MERGE WITH SCHEMA EVOLUTION INTO target
USING source
ON target.id = source.id
WHEN MATCHED THEN
UPDATE SET *
WHEN NOT MATCHED THEN
INSERT *;
This allows the MERGE operation to evolve the target schema where the source and assignments satisfy the supported schema-evolution rules.
For example, a new source column can be added to the target when the operation explicitly enables schema evolution.
See Update table schemas with schema evolution.
Do not confuse this with automatic data-quality validation.
A schema can evolve while bad business data is still bad.
18. MERGE performance — why it can be expensive
A naive MERGE may search a large portion of the target table to find matches.
Conceptually:
Small source | v MERGE | +--> scan huge target | +--> shuffle | +--> compare keys | +--> rewrite affected files
This is why a MERGE can be unexpectedly expensive even when the source contains only a few thousand records.
19. Reduce the target search space
If the business logic tells you which partitions or time ranges can contain matches, include those constraints.
Example:
MERGE INTO events AS t
USING updates AS s
ON t.event_id = s.event_id
AND t.event_date >= current_date() - INTERVAL '7' DAY
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
This can reduce the amount of target data that must be considered.
Databricks explicitly recommends reducing the search space for MERGE using known constraints. Databricks — Delta best practices
20. Small files hurt MERGE
MERGE needs to identify files containing affected records.
If the table contains huge numbers of tiny files:
Target | | | | | | | | | | | | | | many small files
the operation can spend significant time discovering and processing files.
Good Delta table layout and file compaction therefore matter to mutation performance.
See Databricks — Delta best practices.
21. Partitioning is not automatically the answer
A common mistake is:
"MERGE is slow, therefore add more partitions."
Over-partitioning can create small files and operational complexity.
Choose partitioning based on:
- data volume
- access patterns
- cardinality
- lifecycle
- write patterns
- concurrency
For many modern Delta workloads, other layout techniques such as liquid clustering may be preferable.
22. MERGE and concurrency
MERGE is a Delta transaction.
Another concurrent transaction can modify overlapping data or metadata.
Possible outcomes include:
MERGE | +--> validation | +--> conflict | +--> retry / fail
A narrower MERGE predicate can improve both performance and reduce the likelihood of conflicts.
This connects directly to 06.3 ACID, Concurrency & Isolation.
23. Idempotency
Production pipelines must answer:
What happens if the same batch runs twice?
A good MERGE design should produce the same final state when the same logical input is replayed.
For example:
batch_id = 2026-11-07-001
Record the batch identity and make the target mutation deterministic.
Avoid designs where rerunning the same batch blindly creates duplicate rows.
24. Idempotent CDC pattern
A robust architecture is:
Source CDC | raw event ID | deduplicate | deterministic latest event | MERGE | Delta target
Use an immutable event identifier where possible.
For replay handling, maintain enough metadata to distinguish:
- new event
- duplicate event
- late event
- corrected event
25. UPDATE/DELETE and VACUUM
After UPDATE or DELETE, old data files can become inactive table files.
They may still be required for:
- time travel
- long-running readers
- recovery workflows
- streaming readers
VACUUM is responsible for removing old, unreferenced files after retention rules permit it.
Therefore:
UPDATE / DELETE | v New table version | Old files become inactive | v retention | v VACUUM
Do not manually delete Delta files.
Databricks explicitly recommends not modifying Delta data or transaction-log files directly. Databricks — Delta Lake
26. Mutation versus append architecture
For high-volume systems, ask whether every change really needs a MERGE.
Pattern A — Frequent MERGE
CDC → MERGE → Silver
Good for mutable current-state tables.
Pattern B — Append events
Events → append Delta → derive current state
Good when preserving the complete event history is valuable.
Pattern C — Lakeflow CDC
CDC source ↓ AUTO CDC ↓ SCD1 / SCD2 target
Good when managed declarative CDC capabilities fit the platform.
The correct architecture depends on workload semantics, latency, history requirements and operational model.
27. SAP/BW example
Suppose SAP procurement CDC produces:
EBELN EBELP NETPR MENGE AEDAT CHANGE_TYPE
Target:
silver.purchase_order_item
A production pipeline can use:
SAP CDC ↓ Bronze raw events ↓ deduplicate by EBELN + EBELP ↓ validate latest event ↓ MERGE ↓ Silver current-state table ↓ Gold procurement analytics
This is conceptually similar to managing delta/change records in BW, but the physical execution and storage model are different.
28. Finance example
A finance transaction table may receive corrections after the original posting.
Example:
invoice_id = 5001 amount = 1000
Later correction:
invoice_id = 5001 amount = 1100
A deterministic MERGE can update the current state.
But if auditability is required, do not destroy the only copy of the original event.
Use:
immutable finance events + current-state Delta table + audit/history layer
This separates operational state from financial traceability.
29. Common anti-patterns
Anti-pattern 1 — MERGE everything
Use simpler INSERT/UPDATE/DELETE when appropriate.
Anti-pattern 2 — No source deduplication
This causes ambiguous matches and unstable results.
Anti-pattern 3 — Full-table MERGE for tiny daily changes
Reduce the target search space.
Anti-pattern 4 — MERGE without deterministic ordering
Late/replayed events can overwrite newer state.
Anti-pattern 5 — Treating missing source rows as deletes
Only use NOT MATCHED BY SOURCE when the source represents the required authoritative scope.
Anti-pattern 6 — Ignoring small files
Mutation performance can degrade dramatically.
Anti-pattern 7 — Manually modifying Parquet files
This bypasses Delta's transaction protocol.
30. Hands-on Lab — Basic MERGE
Create a target:
CREATE TABLE demo.customer_target (
customer_id INT,
customer_name STRING,
city STRING
) USING DELTA;
Insert initial rows:
INSERT INTO demo.customer_target VALUES
(1, 'A', 'Bengaluru'),
(2, 'B', 'Delhi');
Create a source containing:
- an existing customer with a changed city
- a new customer
Then execute:
MERGE INTO demo.customer_target AS t
USING demo.customer_updates AS s
ON t.customer_id = s.customer_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
Verify:
- existing row updated
- new row inserted
- no duplicate business key
31. Hands-on Lab — Duplicate source detection
Create two source rows with the same customer ID.
Run:
SELECT customer_id, COUNT(*) AS cnt
FROM demo.customer_updates
GROUP BY customer_id
HAVING COUNT(*) > 1;
Then fix the source with ROW_NUMBER and perform the MERGE again.
The goal is to understand why source preparation is part of MERGE design.
32. Hands-on Lab — Conditional delete
Create a CDC source containing:
operation = DELETE
Implement:
WHEN MATCHED
AND s.operation = 'DELETE'
THEN DELETE
Inspect the Delta table history afterward.
33. Hands-on Lab — NOT MATCHED BY SOURCE
Create a source representing the complete authoritative state for a small test domain.
Use:
WHEN NOT MATCHED BY SOURCE
AND t.is_active = true
THEN UPDATE SET
t.is_active = false;
Then repeat with a partial source and observe why the same logic would be unsafe.
34. Hands-on Lab — MERGE performance
Create a larger target and a small update set.
Compare:
Test A
MERGE with only the business key.
Test B
MERGE with an additional known date constraint.
Inspect:
- scan volume
- shuffle
- execution time
- files touched
Use the Spark UI / SQL query profile to explain the difference.
35. Troubleshooting workflow
When MERGE is slow or failing:
Step 1 — Validate source uniqueness
Check duplicate business keys.
Step 2 — Validate the match condition
Is the ON clause selective and correct?
Step 3 — Check source scope
Is the source complete or partial?
Step 4 — Check target layout
Look for excessive small files and poor data locality.
Step 5 — Inspect the physical plan
Look for scans, exchanges, joins and skew.
Step 6 — Check concurrency
Determine whether concurrent writes are causing conflicts.
Step 7 — Check mutation volume
A tiny logical update can still touch many physical files.
Step 8 — Check idempotency
Can the same batch safely run again?
36. Architect decision matrix
| Requirement | Preferred pattern |
|---|---|
| Append new events | INSERT / append |
| Correct known rows | UPDATE |
| Remove known rows | DELETE |
| Upsert current state | MERGE |
| CDC I/U/D | MERGE or managed CDC |
| Full source synchronization | MERGE + NOT MATCHED BY SOURCE |
| Immutable audit history | Append event table |
| SCD1 | MERGE / managed CDC |
| SCD2 | MERGE or managed CDC |
| Very high mutation frequency | Reconsider table/mutation architecture |
37. Interview questions
Foundation
- What is MERGE in Delta Lake?
- What is the difference between MERGE and UPDATE?
- When would you use DELETE?
- What does WHEN MATCHED mean?
- What does WHEN NOT MATCHED mean?
- What is WHEN NOT MATCHED BY SOURCE?
Engineering
- Why can MERGE fail because of duplicate source rows?
- How do you deduplicate a CDC source?
- How do you make MERGE idempotent?
- How do you optimize MERGE?
- Why do small files affect MERGE?
- How does MERGE interact with Delta concurrency?
- What happens to old files after UPDATE or DELETE?
- When should you use NOT MATCHED BY SOURCE?
Architect
- Would you MERGE every CDC event directly into a 20 TB table?
- How would you design SAP CDC ingestion?
- How would you prevent replayed events from overwriting newer data?
- When would you choose append-only events over current-state MERGE?
- How would you design SCD2 at enterprise scale?
- How would you minimize MERGE conflicts across concurrent pipelines?
38. Production checklist
- Define the business key explicitly.
- Validate source uniqueness before MERGE.
- Make source ordering deterministic.
- Use explicit conditions for business-specific updates/deletes.
- Avoid treating partial sources as complete snapshots.
- Reduce the target search space where possible.
- Monitor small files and table layout.
- Inspect physical execution for expensive MERGEs.
- Design for replay and idempotency.
- Understand concurrent writers.
- Keep immutable audit/event data when required.
- Do not manually modify Delta files.
- Use VACUUM according to your retention and reader requirements.
- Prefer managed CDC capabilities where they simplify operations.
- Load-test mutation patterns at realistic scale.
39. Architect mental model
The most useful way to remember Delta mutations is:
UPDATE = change known target state
DELETE = remove known target state
MERGE = reconcile source state with target state
And at physical scale:
Business mutation ↓ Match / predicate ↓ Read affected data ↓ Write replacement files ↓ Delta transaction ↓ New snapshot ↓ Retention / VACUUM lifecycle
The key architect question is not:
"Can Delta update this row?"
It is:
"What is the correct state-transition model, and how cheaply and safely can the lakehouse maintain it?"