Using UPSERT Operations

1. Understanding INSERT ON CONFLICT

ConceptDetail
Conflict targetUnique/exclusion constraint or column list
DO NOTHINGSkip conflicting rows
DO UPDATEApply updates to existing row
excluded.colRefers to candidate row's value

2. Using DO NOTHING

INSERT INTO customers (email, full_name)
VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO NOTHING;

3. Using DO UPDATE SET

INSERT INTO customers (email, full_name, updated_at)
VALUES ('alice@example.com', 'Alice W.', now())
ON CONFLICT (email) DO UPDATE
   SET full_name = excluded.full_name,
       updated_at = excluded.updated_at;

4. Specifying Conflict Target

FormExample
Column listON CONFLICT (email)
Constraint nameON CONFLICT ON CONSTRAINT customers_email_key
Expression indexON CONFLICT (lower(email))
Partial indexMust match WHERE predicate

5. Using WHERE Clause in DO UPDATE

INSERT INTO inventory (sku, qty, updated_at)
VALUES ('A100', 5, now())
ON CONFLICT (sku) DO UPDATE
   SET qty = excluded.qty, updated_at = excluded.updated_at
 WHERE inventory.qty < excluded.qty;     -- only bump if larger

6. Using excluded.*

INSERT INTO counters (key, value, updated_at)
VALUES ('hits', 1, now())
ON CONFLICT (key) DO UPDATE
   SET value = counters.value + excluded.value,
       updated_at = excluded.updated_at;
AliasRefers To
excludedRow proposed by INSERT
table_nameExisting conflicting row

7. Using RETURNING with UPSERT

INSERT INTO customers (email, full_name)
VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO UPDATE SET full_name = excluded.full_name
RETURNING id, (xmax = 0) AS was_inserted;
Note: xmax = 0 identifies whether the row was newly inserted vs updated.

8. Handling Unique Constraint Violations

PatternWhen
ON CONFLICT DO NOTHINGIdempotent inserts
ON CONFLICT DO UPDATELast-write-wins merge
Savepoint + retryMulti-constraint cases

9. Handling Primary Key Conflicts

INSERT INTO sessions (id, user_id, last_seen)
VALUES ('abc', 42, now())
ON CONFLICT (id) DO UPDATE SET last_seen = excluded.last_seen;

10. Using UPSERT with Multiple Columns

INSERT INTO daily_stats (date, metric, value)
VALUES (current_date, 'signups', 5)
ON CONFLICT (date, metric) DO UPDATE
   SET value = daily_stats.value + excluded.value;
RequiredDetail
Unique constraintMust exist on (date, metric)
Order mattersColumn list must match an existing unique/exclusion constraint