Upsert / Merge
Insert a row, or update an existing row when a key conflict occurs.
upsert merge insert on conflict insert on duplicate key update insert or replace
insert update conflict handling duplicate key
| Database | Status | Syntax Family | Min Version |
|---|---|---|---|
| PostgreSQL | full | insert-on-conflict | 9.5 |
| MySQL | full | insert-on-duplicate-key | - |
| MariaDB | full | insert-on-duplicate-key | - |
| Oracle | full | merge | - |
| SQL Server | full | merge | - |
| SQLite | full | insert-on-conflict | 3.24.0 |
| Redshift | full | merge | - |
| DB2 | full | merge | - |
| Snowflake | full | merge | - |
| BigQuery | full | merge | - |
Compare Databases
Database Details
PostgreSQL
full
INSERT INTO users (id, email)
VALUES (1, 'a@example.com')
ON CONFLICT (id) DO UPDATE
SET email = EXCLUDED.email;- Conflict target must be explicitly specified.
- Supports DO NOTHING as an alternative to DO UPDATE.
- Also supports the SQL-standard MERGE syntax as of PostgreSQL 15.
Docs: INSERT ... ON CONFLICT
MySQL
full
INSERT INTO users (id, email)
VALUES (1, 'a@example.com')
ON DUPLICATE KEY UPDATE email = VALUES(email);- Triggered by any unique key violation, not just the primary key.
- VALUES() in UPDATE clause is deprecated in 8.0.20+; use alias syntax instead.
- No support for conditional conflict targets.
MariaDB
full
INSERT INTO users (id, email)
VALUES (1, 'a@example.com')
ON DUPLICATE KEY UPDATE email = VALUES(email);- Same syntax as MySQL ON DUPLICATE KEY UPDATE.
- Also supports REPLACE INTO as a delete-then-insert alternative.
Oracle
full
MERGE INTO users t
USING (SELECT 1 AS id, 'a@example.com' AS email FROM dual) s
ON (t.id = s.id)
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- Uses SQL-standard MERGE syntax.
- Supports WHEN MATCHED and WHEN NOT MATCHED clauses with conditions.
Docs: MERGE
SQL Server
full
MERGE INTO users AS t
USING (VALUES (1, 'a@example.com')) AS s(id, email)
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- Uses MERGE syntax; statement must end with semicolon.
- Supports OUTPUT clause for RETURNING-like behavior.
- MERGE has had concurrency bugs in older versions; check KB articles.
Docs: MERGE
SQLite
full
INSERT INTO users (id, email)
VALUES (1, 'a@example.com')
ON CONFLICT(id) DO UPDATE SET email = excluded.email;- Syntax similar to PostgreSQL but uses lowercase 'excluded' table reference.
- Also supports INSERT OR REPLACE, but that deletes and re-inserts.
Docs: UPSERT
Redshift
full
MERGE INTO users AS t
USING (VALUES (1, 'a@example.com')) AS s(id, email)
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- MERGE statement added in April 2023.
- Supports WHEN MATCHED (UPDATE or DELETE) and WHEN NOT MATCHED (INSERT) clauses.
- No INSERT ... ON CONFLICT syntax; MERGE is the native upsert mechanism.
Docs: MERGE
DB2
full
MERGE INTO users AS t
USING (VALUES (1, 'a@example.com')) AS s(id, email)
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- Supports SQL-standard MERGE.
- Also supports multiple WHEN MATCHED clauses with conditions.
Docs: MERGE
Snowflake
full
MERGE INTO users AS t
USING (SELECT 1 AS id, 'a@example.com' AS email) AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- Uses MERGE syntax.
- Supports multiple WHEN clauses.
Docs: MERGE
BigQuery
full
MERGE INTO users AS t
USING (SELECT 1 AS id, 'a@example.com' AS email) AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email);- Uses MERGE syntax.
- Source and target must be different tables.
Docs: MERGE