Merge incoming rows by key
Merging is different from updating because it involves comparing incoming rows to existing rows by key, and then choosing what to do based on whether the key exists in the target table or not. Usemerge_insert("id") in Python or the corresponding SDK builder.
In merge operations, rows are split into three groups:
- Matched: key exists in both source and target.
- Not matched: key exists only in source.
- Not matched by source: key exists only in target.
target. prefix for the
existing table row and source. for the incoming row, for example
target.last_update < source.last_update.
Choose a key that identifies the same record in the source and target. Check
for duplicate and null keys before merging. A primary-key declaration does not
enforce uniqueness on ordinary writes, and append operations do not deduplicate
records for you.
A scalar index on the key can help matching. Keep it current and measure the
actual workload rather than assuming every merge uses an index. Enterprise may
reject a merge when the unindexed scan exceeds its configured limit; inspect
that error and the index state before retrying.
Choose a merge policy
The following examples are independent. Each starts from the table shown in
its code, so the expected results do not depend on running the previous example.
Python blocks include their own temporary connection. For a full TypeScript or
Rust program, see Complete write workflow.
Update matched rows only
This updates keys that already exist in the target table. Source rows with new keys are ignored.Insert unmatched rows only
This inserts only brand-new keys from the source. Existing keys are left unchanged.Update matched rows and insert unmatched rows
Use bothwhen_matched_update_all() and when_not_matched_insert_all() when you want to update existing keys and insert missing keys in one operation.
This is a conventional upsert.
Delete target rows that are missing from source
Usewhen_not_matched_by_source_delete() only when the incoming data is the
complete set for the target scope you intend to synchronize. An incremental
batch is usually not a complete source: deleting everything absent from it
could remove unrelated target records. Inspect the unmatched target keys first.
Here, IDs 2 and 3 are updated and ID 1 is deleted. This policy synchronizes
the target to the supplied source set.
Use partial columns in merge updates
Merge updates do not require you to provide values for all columns. You can provide only a subset of columns in source rows. For matched rows, only the provided columns are updated.
Note that in the example above, when
merge_insert creates a new row, any missing columns are written as null. If a missing column is non-nullable in your schema, the insert will fail.
Check the merged records
Inspect the keys that should have changed, confirm that unrelated rows remain, and check inserted or deleted counts when the SDK returns them. Repeating a merge is only safe when its keys, conditions and update values produce the intended result on retry.Embeddings and expression predicates
When a table uses an embedding definition, behavior depends on which source and vector fields the incoming records provide. Do not assume a partial metadata update recomputes embeddings. Inspect the resulting vector when changing the input used to generate it. Rust’swhen_matched_update_all_expr and
when_not_matched_by_source_delete_expr accept DataFusion expressions for
LanceDB Embedded tables. Use the string predicate variants for Enterprise tables.
Complete write workflow
Complete examples: create, append, update, upsert and delete
Complete examples: create, append, update, upsert and delete
Each program creates a temporary local database, performs the five operations,
prints the result, and removes its temporary data when finished.After the operations, Alice has 10 logins, Bobby has 21, and Charlie has 6.
Dana is selected for deletion and is absent from the final result.
- Python: install
lancedb, save the code asingestion.py, and runpython ingestion.py. - TypeScript: install
@lancedb/lancedb, save the code asingestion.mts, and runnode ingestion.mtswith Node.js 22.18 or later. - Rust: download the complete Rust project,
extract it, and run
cargo run --locked. It includessrc/main.rs, the Cargo manifest and a lockfile for the example’s dependencies. The code is shown below.