Upserts (ON CONFLICT)
An "upsert" is a single statement that inserts a row if it does not already exist, or updates it if it does — the name is a blend of insert and update. PostgreSQL's idiomatic way to do this is INSERT ... ON CONFLICT, which avoids the classic race condition of first checking whether a row exists and then deciding whether to insert or update it in a second statement.
The basic pattern
ON CONFLICT is attached to an INSERT and names the column (or columns) that might collide with an existing row, followed by what to do about it: DO NOTHING to silently skip the insert, or DO UPDATE SET to update the existing row instead.
Insert a product, or update its stock if the SKU already exists
INSERT INTO products (sku, name, unit_price, stock_qty)
VALUES ('SKU-1001', 'Wireless Mouse', 24.99, 150)
ON CONFLICT (sku)
DO UPDATE SET stock_qty = products.stock_qty + EXCLUDED.stock_qty;INSERT 0 1
Running that exact statement again — say, as part of restocking the same SKU a second time — does not fail with a duplicate-key error. Instead, because a row with sku = 'SKU-1001' now exists, PostgreSQL runs the DO UPDATE SET instead of the INSERT, adding the new shipment's quantity onto the existing stock.
Understanding EXCLUDED
EXCLUDED is a special, temporary row name available only inside the DO UPDATE SET clause. It refers to the row that would have been inserted — that is, the values from the VALUES clause of the INSERT — as opposed to the row that already exists in the table (which is referred to by the table's own name or an alias).
Replacing vs. accumulating a value with EXCLUDED
-- Replace the price outright with whatever was just supplied
INSERT INTO products (sku, name, unit_price, stock_qty)
VALUES ('SKU-1002', 'Mechanical Keyboard', 79.00, 40)
ON CONFLICT (sku)
DO UPDATE SET unit_price = EXCLUDED.unit_price;
-- Accumulate onto the existing stock quantity instead of replacing it
INSERT INTO products (sku, name, unit_price, stock_qty)
VALUES ('SKU-1002', 'Mechanical Keyboard', 79.00, 40)
ON CONFLICT (sku)
DO UPDATE SET stock_qty = products.stock_qty + EXCLUDED.stock_qty;DO NOTHING
When a conflicting row should simply be left alone rather than updated, DO NOTHING skips the insert entirely for that row, without raising an error.
Silently skip duplicates
INSERT INTO products (sku, name, unit_price, stock_qty)
VALUES ('SKU-1001', 'Wireless Mouse', 24.99, 150)
ON CONFLICT (sku) DO NOTHING;ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
INSERT ... ON CONFLICT (column) DO UPDATE SET ... / DO NOTHING is PostgreSQL's upsert.
EXCLUDED refers to the row that would have been inserted, distinct from the existing row already in the table.
DO NOTHING silently skips the insert on conflict; DO UPDATE SET modifies the existing row instead.
The conflict target column(s) must be covered by a unique or exclusion constraint.