MERGE INTO is one of the most-used data operations in Delta Lake. Because a single statement can update matched rows, insert unmatched rows, and delete rows you no longer want, it's widely used to implement UPSERT (Update or Insert), deduplication, and Slowly Changing Dimension (SCD) patterns. It's a high-frequency topic across every Data Engineer-track Databricks certification exam.
MERGE INTO is built from a target table, a source dataset, a match condition, an action for matched rows, and an action for unmatched rows.
MERGE INTO silver.customers AS t
USING bronze.raw_customers AS s
ON t.customer_id = s.customer_id
-- Matched rows: update
WHEN MATCHED AND s.updated_at > t.updated_at THEN
UPDATE SET
t.name = s.name,
t.email = s.email,
t.updated_at = s.updated_at
-- Unmatched rows: insert
WHEN NOT MATCHED THEN
INSERT (customer_id, name, email, created_at, updated_at)
VALUES (s.customer_id, s.name, s.email, s.created_at, s.updated_at)
-- Rows missing from source: delete (optional)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;WHEN NOT MATCHED BY SOURCE was added in Delta Lake 2.4 and lets you handle rows that exist in the target but not in the source — i.e., rows that have been deleted upstream.
The most fundamental pattern: update on key match, insert otherwise. This is the most common pattern when ingesting data into the Silver layer.
-- Simple UPSERT
MERGE INTO silver.products t
USING staging.new_products s
ON t.product_id = s.product_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;SET * and INSERT * mean "all columns." These shortcuts work when the source and target share the same column structure.
The Bronze layer often receives the same record multiple times (retries, duplicate sends, etc.). The standard deduplication pattern uses MERGE plus ROW_NUMBER to ingest only the latest record per key.
MERGE INTO silver.events t
USING (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY event_id
ORDER BY event_timestamp DESC
) AS rn
FROM bronze.raw_events
WHERE _ingested_at > current_timestamp() - INTERVAL 1 HOUR
QUALIFY rn = 1
) s
ON t.event_id = s.event_id
WHEN MATCHED AND s.event_timestamp > t.event_timestamp
THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;The key is to deduplicate on the source side with ROW_NUMBER before running MERGE. If the MERGE source contains duplicates, multiple matches will occur for the same key and the statement will fail.
SCD Type 1 simply overwrites the existing record whenever a change occurs. History is not preserved. Use it when only the latest state matters.
-- SCD Type 1: overwrite on any change
MERGE INTO dim.customers t
USING staging.customer_updates s
ON t.customer_id = s.customer_id
WHEN MATCHED AND (
t.name != s.name OR
t.email != s.email OR
t.address != s.address
) THEN UPDATE SET
t.name = s.name,
t.email = s.email,
t.address = s.address,
t.updated_at = current_timestamp()
WHEN NOT MATCHED THEN INSERT *;SCD Type 2 expires the prior record and inserts the new version as a new row, preserving the complete change history. Lifecycle is managed via an is_current flag and a validity window (effective_from / effective_to).
-- SCD Type 2: Step 1 - expire existing records
MERGE INTO dim.customers t
USING staging.customer_updates s
ON t.customer_id = s.customer_id AND t.is_current = true
WHEN MATCHED AND (t.name != s.name OR t.email != s.email) THEN
UPDATE SET
t.is_current = false,
t.effective_to = current_date();
-- SCD Type 2: Step 2 - insert new records
INSERT INTO dim.customers
SELECT
customer_id,
name,
email,
true AS is_current,
current_date() AS effective_from,
'9999-12-31' AS effective_to
FROM staging.customer_updates s
WHERE NOT EXISTS (
SELECT 1 FROM dim.customers t
WHERE t.customer_id = s.customer_id
AND t.is_current = true
AND t.name = s.name
AND t.email = s.email
);SCD Type 2 is hard to express in a single MERGE, so the typical implementation uses two steps: UPDATE then INSERT. Delta Live Tables (DLT) ships a dedicated APPLY CHANGES INTO statement for SCD Type 2 that lets you express the same logic far more concisely. This syntax is also fair game on the exams.
To MERGE incremental data from Structured Streaming into the Silver layer, use foreachBatch. It hands each micro-batch result to you as a DataFrame, which you then MERGE.
def upsert_to_silver(batch_df, batch_id):
# Deduplicate on the source side
deduped = batch_df.dropDuplicates(["customer_id"])
deduped.createOrReplaceTempView("updates")
spark.sql("""
MERGE INTO silver.customers t
USING updates s
ON t.customer_id = s.customer_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *
""")
(spark.readStream
.format("delta")
.table("bronze.raw_customers")
.writeStream
.foreachBatch(upsert_to_silver)
.option("checkpointLocation", "/checkpoint/silver_customers")
.trigger(availableNow=True)
.start()
)| Challenge | Mitigation | Effect |
|---|---|---|
| Match-condition scan is slow | Z-Order / Liquid Cluster on the match keys | Dramatically fewer files scanned |
| Duplicates in the source | Pre-deduplicate with ROW_NUMBER + QUALIFY | Prevents MERGE errors |
| Large-volume MERGE | Split the work via foreachBatch | Stable memory and easier failure recovery |
| Accumulation of unused files | Run OPTIMIZE + VACUUM after MERGE | Maintains read performance |
Data Engineer Associate / Professional
問題 1
Records with the same customer_id sometimes arrive multiple times in the Bronze layer. The Silver customers table should keep only the latest record per customer_id. Which implementation is most appropriate?
正解: A
If the MERGE source contains duplicates on the same key the statement errors out, so the correct pattern is to narrow the source to the latest record per key with ROW_NUMBER + QUALIFY before running MERGE. B is wrong because you can't use ROW_NUMBER inside MERGE itself. C is a plain insert rather than an UPSERT, so duplicates remain. D is wrong because Delta Lake doesn't support UNIQUE constraints (only CHECK constraints).
What's the difference between MERGE INTO and INSERT OVERWRITE?
MERGE INTO evaluates match conditions row-by-row and combines updates for matched rows, inserts for unmatched rows, and conditional deletes into a single statement. INSERT OVERWRITE replaces data wholesale at the partition level. Use MERGE when you need incremental diff processing against existing data, and INSERT OVERWRITE when you want to fully refresh an entire partition.
How do I improve MERGE performance?
The most effective approach is to Z-Order or Liquid Cluster the table on the columns used in the match condition. This dramatically reduces the number of files scanned. Deduplicating the source data up front to shrink the MERGE workload itself is also important. For large MERGE workloads, splitting the work into micro-batches with foreachBatch is another effective option.
How is MERGE tested on the certification exams?
MERGE is the most frequently tested SQL operation on both Data Engineer Associate and Professional. The main scope includes the basic syntax (USING / ON / WHEN MATCHED / WHEN NOT MATCHED), SCD Type 1/2 implementation patterns, deduplication applications, and combining MERGE with foreachBatch. The recently added WHEN NOT MATCHED BY SOURCE clause (for handling rows missing from the source) is also fair game.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
Databricks Certifications: All 7 Exams, Difficulty & Study Plan (2026)
Complete guide to all 7 Databricks certifications — Data Eng...
Databricks Exam Difficulty Ranking: All 7 Certs Compared (2026)
Every Databricks certification ranked by difficulty, with st...
Databricks Study Guide: Fastest Pass Route & Time Estimates (2026)
How to pass Databricks certifications efficiently. Official ...
Databricks Data Engineer Associate: Complete Guide (2026)
Domain-by-domain breakdown of the Databricks Certified Data ...
Databricks Data Engineer Professional: Complete Guide (2026)
Tactics for the Databricks Certified Data Engineer Professio...