Upsert

One database operation that inserts a new row or updates the matching row when it already exists.

insert it or update it if it already existscreate or update in one querysave without knowing if the row existsupcertinsert on conflictavoid duplicate key errorsthe webhook arrived twice and created two rowssync this record by external id

See it

Live demo coming soon

What it is

An upsert asks the database to insert a row, but update the existing one if that insert collides with a declared key. PostgreSQL spells it INSERT ... ON CONFLICT DO UPDATE. The whole choice happens as one database statement, so it avoids the race in 'SELECT first, then INSERT or UPDATE'.

The word is not standard SQL, which is why every database spells it differently. MySQL has had INSERT ... ON DUPLICATE KEY UPDATE for years, PostgreSQL only gained ON CONFLICT in 9.5 and the standard MERGE statement in 15, and ORMs paper over the gap with a single method such as Prisma's upsert. Check what your layer actually emits before you trust it to be one statement.

Reach for it when a stable identity already tells you which logical row you are syncing: a provider's event id, a user's setting, or a daily aggregate keyed by account and date. The conflict target must be backed by a primary key, unique constraint, or suitable unique index. Without that identity rule, the database cannot know which row counts as already present.

Gotcha: an upsert can overwrite newer data just as atomically as it can write correct data. Update only the columns the incoming source owns, and add a timestamp or version condition when stale events can arrive late. It also does not make side effects idempotent: sending an email before every upsert still sends the email twice.

Ask AI for it

Replace this SELECT-then-write flow with one PostgreSQL INSERT ... ON CONFLICT statement. Add the UNIQUE constraint that defines identity, name those columns in the conflict target, use EXCLUDED only for fields the incoming source owns, and add a WHERE condition to the DO UPDATE branch when an older updated_at value must not replace a newer one. Use RETURNING to return the saved row in the same round trip. For multi-row batches, sort the rows by the conflict key before sending so two concurrent batches cannot deadlock on overlapping keys, and retry SQLSTATE 40P01. Show the SQL and tests for first insert, repeated input, changed input, a late out-of-order event, and two concurrent calls with the same key.

You might have meant

unique constraintidempotencyrace conditiondatabase transactionoptimistic locking