Upsert

Learn how to use PostgreSQL upsert with INSERT ON CONFLICT to insert a new row or update an existing one in a single statement.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

PostgreSQL upsert uses INSERT ... ON CONFLICT to insert a row if it does not exist or update it if a conflict occurs on a unique constraint. Use DO UPDATE SET with the EXCLUDED keyword to reference the values you attempted to insert.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

Upsert is a combination of update and insert. PostgreSQL does not have a dedicated UPSERT keyword, but supports the operation through the INSERT ... ON CONFLICT statement. When a conflict occurs on a unique constraint or unique index, you can either do nothing or update the existing row with the new values.

INSERT ... ON CONFLICT syntax

The basic syntax specifies a conflict target and the action to take when that conflict occurs:

INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON CONFLICT (conflict_column)
DO UPDATE SET
  column1 = EXCLUDED.column1,
  column2 = EXCLUDED.column2;

The EXCLUDED keyword refers to the row that was proposed for insertion but was rejected due to the conflict. Use DO NOTHING instead of DO UPDATE if you want to silently skip duplicates.

Practical upsert examples

Create an inventory table and insert some sample rows:

CREATE TABLE inventory (
  id      INT PRIMARY KEY,
  name    VARCHAR(255) NOT NULL,
  price   DECIMAL(10, 2) NOT NULL,
  quantity INT NOT NULL
);

INSERT INTO inventory (id, name, price, quantity)
VALUES (1, 'A', 15.99, 100),
       (2, 'B', 25.49,  50),
       (3, 'C', 19.95,  75);

Update an existing row when a conflict on id occurs:

INSERT INTO inventory (id, name, price, quantity)
VALUES (1, 'A', 16.99, 120)
ON CONFLICT (id)
DO UPDATE SET
  price    = EXCLUDED.price,
  quantity = EXCLUDED.quantity;
INSERT 0 1

Verify the update:

SELECT * FROM inventory WHERE id = 1;
 id | name | price | quantity
----+------+-------+----------
  1 | A    | 16.99 |      120

Insert a brand-new row when no conflict exists (id 4 is new):

INSERT INTO inventory (id, name, price, quantity)
VALUES (4, 'D', 29.99, 20)
ON CONFLICT (id)
DO UPDATE SET
  price    = EXCLUDED.price,
  quantity = EXCLUDED.quantity;
SELECT * FROM inventory ORDER BY id;
 id | name | price | quantity
----+------+-------+----------
  1 | A    | 16.99 |      120
  2 | B    | 25.49 |       50
  3 | C    | 19.95 |       75
  4 | D    | 29.99 |       20

Production tips for upsert

  • Always specify a conflict target — ON CONFLICT (column) or a named constraint — so the statement fails loudly if the schema changes.
  • Use EXCLUDED.column to reference proposed values in the SET clause; mixing EXCLUDED with table-qualified names lets you build conditional updates.
  • Add a WHERE clause to the DO UPDATE block to skip no-op updates: DO UPDATE SET price = EXCLUDED.price WHERE inventory.price <> EXCLUDED.price.
  • For PostgreSQL 15+, consider MERGE when you need a single statement that also handles deletes.
  • Validate upsert behavior against concurrent writers using SERIALIZABLE isolation if data correctness is critical.

Continue in Modifying Data: MERGE.

Related in this section: INSERT · INSERT Multiple Rows · UPDATE

Frequently Asked Questions

Does PostgreSQL have an UPSERT statement?

PostgreSQL does not have a dedicated UPSERT keyword. Instead, it implements the upsert pattern through INSERT ... ON CONFLICT. If you are on PostgreSQL 15 or later, you can also use the MERGE statement.

What does EXCLUDED mean in ON CONFLICT DO UPDATE?

EXCLUDED is a special table reference that contains the values from the row that was rejected due to the conflict. Use EXCLUDED.column_name in the SET clause to reference the values you attempted to insert.

Can I upsert on multiple columns?

Yes. If your conflict target involves a multi-column unique constraint, reference it by constraint name: ON CONFLICT ON CONSTRAINT my_unique_constraint DO UPDATE SET ....

How do I silently ignore duplicate inserts without updating?

Use DO NOTHING: INSERT INTO table (col) VALUES (val) ON CONFLICT (col) DO NOTHING. The statement succeeds and returns a count of 0 for rows that conflicted.

Is INSERT ON CONFLICT atomic?

Yes. The conflict check and the update or insert are performed atomically within a single statement. If the statement is inside an explicit transaction and the transaction rolls back, neither the insert nor the update is persisted.