How does a MERGE evaluate multiple WHEN MATCHED arms carrying AND conditions?
answer
- arms behave like CASE branches
- first satisfied arm wins, others skipped
- one action per row, never two
- an arm with no AND matches everything
- order the specific case before the general
basics
~20 sArms are tested in the order written, and the first one whose condition holds fires — at most one action per row. That ordering lets a single MERGE delete tombstoned rows, update changed ones, and leave unchanged rows alone.
solid answer
~40 sEach arm may carry an extra predicate, as in `WHEN MATCHED AND s.op = 'D' THEN DELETE`. For a given row the engine walks the arms of the applicable kind top to bottom and applies the first whose condition is satisfied; the rest are not considered, so no row gets two actions. That makes arm order semantically significant: put the most specific case first. An arm written without an `AND` matches everything, so any arm of the same kind after it is unreachable — write the unconditional one last, or leave it out. Typical delta-load ordering is delete-on-tombstone, then update-when-something-changed, then insert. Engines differ on how many arms of each kind they permit, so check yours before relying on more than one or two.
code
sql · 10 linesMERGE INTO product t
USING product_delta s
ON (t.sku = s.sku)
WHEN MATCHED AND s.op = 'D' THEN
DELETE
WHEN MATCHED AND (t.price <> s.price OR t.name <> s.name) THEN
UPDATE SET price = s.price, name = s.name
WHEN NOT MATCHED AND s.op <> 'D' THEN
INSERT (sku, name, price)
VALUES (s.sku, s.name, s.price);go deeper
Know that arms are checked in order like CASE branches and only the first matching one runs, so a row is never updated and deleted by the same MERGE.
Explain arm ordering and AND conditions, including why an unconditional arm must come last and how a matched row with no true condition is simply left alone — the standard way to skip no-op updates.
Design the delta policy deliberately: tombstones before change detection, null-safe comparisons on nullable columns, and awareness that engines cap how many arms of a kind you may write.
Judge when the reconciliation has outgrown one statement. Several named steps against a staging table are testable and reviewable; a five-arm MERGE is compact but its ordering bugs surface as quietly missing rows.
## Arms are a chain of conditions, not a set of independent rules A MERGE arm has the shape `WHEN MATCHED [AND <condition>] THEN <action>` or `WHEN NOT MATCHED [AND <condition>] THEN INSERT ...`. You may write more than one arm of a kind. For each row the engine considers the arms of the applicable kind **in the order they appear** and applies the **first** one whose condition evaluates true. Everything after that is skipped. Two consequences follow immediately: - A row receives **at most one** action per MERGE. Arms are not filters that each get a turn. - **Order is meaning.** Swapping two arms can change the result, exactly as reordering the branches of a `CASE` expression can. ## A delta load reads top-down ```sql MERGE INTO product t USING product_delta s ON (t.sku = s.sku) WHEN MATCHED AND s.op = 'D' THEN DELETE WHEN MATCHED AND (t.price <> s.price OR t.name <> s.name) THEN UPDATE SET price = s.price, name = s.name WHEN NOT MATCHED AND s.op <> 'D' THEN INSERT (sku, name, price) VALUES (s.sku, s.name, s.price); ``` Read it as a policy: *if the feed says this SKU is gone, delete it; otherwise if anything actually changed, update it; otherwise leave it alone; and if the SKU is new and not a deletion, insert it.* Note what the third case gets you for free — a matched row whose values are identical satisfies no matched arm, so nothing is written for it. That is the idiomatic way to suppress no-op updates in a MERGE, and it matters because a no-op UPDATE is still a write as far as the engine, triggers and any change-data capture are concerned. Swap the first two arms and the delete never happens for a row whose values also differ — the update arm would claim it first. The ordering encodes the priority of the rules. ## Unconditional arms terminate the chain `WHEN MATCHED THEN UPDATE ...` with no `AND` is true for every matched row. Any later `WHEN MATCHED` arm is therefore dead code; engines typically reject or warn about the unreachable arm rather than silently ignoring it. The habit that avoids the problem is simple: within each kind, write the conditional arms first and the unconditional catch-all — if you want one at all — last. ## The NULL caveat in arm conditions A change-detection condition like `t.price <> s.price` returns *unknown*, not true, when either side is NULL, and an arm only fires on true. So a row whose price went from NULL to 9.99 would not be seen as changed. When the columns are nullable you need a null-safe comparison — standard SQL provides `IS DISTINCT FROM` for exactly this: `WHEN MATCHED AND (t.price IS DISTINCT FROM s.price OR t.name IS DISTINCT FROM s.name) THEN ...`. Engine support for that predicate varies, so verify it before relying on it. ## Portability of multi-arm MERGE The standard permits repeated arms with conditions, but implementations impose their own limits — SQL Server, for instance, allows at most two `WHEN MATCHED` clauses and requires the first of them to carry a condition. If your policy has three or four matched cases, either fold them into fewer arms with richer conditions and `CASE` expressions in the SET list, or split the work into separate statements. Check the specific engine's documentation for the cap rather than assuming the standard's permissiveness. ## When one MERGE is too clever A statement with five arms and nested conditions is compact but hard to test and hard to explain in review, and a mistake in arm order is invisible until the wrong rows disappear. If the reconciliation rules are genuinely complex — several kinds of tombstone, effective-dated history, conditional promotion between statuses — expressing them as a few clearly named statements against a staging table is often the more maintainable choice, and it makes each rule independently testable. The multi-arm MERGE earns its keep when the policy is small enough to read in one screen.
- What happens to a matched row when no matched arm's condition is true?Nothing at all. The row was classified as matched, so it can never reach the not-matched arm, and with every matched condition false no action applies. That is precisely how you suppress no-op updates for rows whose values are unchanged.
- Why can an unconditional WHEN MATCHED arm not be written first?Because it is true for every matched row, so it always wins and any later matched arm becomes unreachable. Engines generally reject or flag the dead arm. Write conditional arms first and the catch-all, if any, last.
- Why might a 'has anything changed' arm miss rows involving NULLs?`t.price <> s.price` evaluates to unknown when either side is NULL, and an arm fires only on true, so a change from NULL to a value goes unnoticed. Use a null-safe comparison such as `IS DISTINCT FROM` where the engine supports it.
saying these in an interview costs you the question
- Thinks every arm whose condition holds gets applied
- Says arm order does not matter, only the conditions
- Puts the unconditional arm first and expects later arms to run
- Assumes a row can be deleted and then re-inserted in one MERGE
- Uses <> on nullable columns to detect changes