UPSERT with ON CONFLICT
Insert or update rows atomically using PostgreSQL ON CONFLICT clause with partial indexes and conditions.
-- Basic upsert: insert or update on unique constraint
INSERT INTO users (email, name, updated_at)
VALUES ('ada@example.com', 'Ada Lovelace', NOW())
ON CONFLICT (email)
DO UPDATE SET
name = EXCLUDED.name,
updated_at = EXCLUDED.updated_at;
-- Upsert with conditional update (only if data changed)
INSERT INTO products (sku, price, stock)
VALUES ('WIDGET-01', 29.99, 100)
ON CONFLICT (sku)
DO UPDATE SET
price = EXCLUDED.price,
stock = EXCLUDED.stock
WHERE products.price != EXCLUDED.price
OR products.stock != EXCLUDED.stock;
-- Bulk upsert with RETURNING
INSERT INTO tags (name, slug)
VALUES
('JavaScript', 'javascript'),
('TypeScript', 'typescript'),
('Python', 'python')
ON CONFLICT (slug) DO NOTHING
RETURNING id, name, slug;
-- Upsert with composite key
INSERT INTO user_settings (user_id, key, value)
VALUES (1, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value;Use Cases
- Sync external data sources
- Idempotent API handlers
- Batch data imports
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Keyset Pagination vs Offset
Efficient keyset (cursor) pagination pattern compared to traditional OFFSET for large datasets in PostgreSQL.
Full-Text Search with Ranking
PostgreSQL full-text search using tsvector, tsquery, and ts_rank with trigram similarity for fuzzy matching.
Window Functions with RANK and LAG
Use window functions to rank rows, calculate running totals, and compare with previous rows without self-joins.
JSON Aggregation and Querying
Aggregate related rows into JSON arrays and query JSONB columns with PostgreSQL native JSON operators.