Insert, update & upsert

Upsert (insert or update) in SQL

3 min · updated June 16, 2026

“Insert, or update if it’s already there.” Three different spellings.

-- PostgreSQL, SQLite, DuckDB
INSERT INTO users (id, name, visits)
VALUES (1, 'Ann', 1)
ON CONFLICT (id) DO UPDATE
  SET name   = EXCLUDED.name,
      visits = users.visits + EXCLUDED.visits;   -- EXCLUDED = the row you tried to insert

-- MySQL
INSERT INTO users (id, name, visits)
VALUES (1, 'Ann', 1) AS new            -- row alias (MySQL 8.0.19+)
ON DUPLICATE KEY UPDATE
  name   = new.name,
  visits = users.visits + new.visits;

-- BigQuery, Snowflake, PostgreSQL 15+
MERGE INTO users AS t
USING (SELECT 1 AS id, 'Ann' AS name, 1 AS visits) AS s
ON t.id = s.id
WHEN MATCHED THEN
  UPDATE SET name = s.name, visits = t.visits + s.visits
WHEN NOT MATCHED THEN
  INSERT (id, name, visits) VALUES (s.id, s.name, s.visits);

Insert, but do nothing on conflict (skip duplicates):

INSERT INTO users (id, name) VALUES (1, 'Ann') ON CONFLICT (id) DO NOTHING;  -- Postgres / SQLite / DuckDB
INSERT IGNORE INTO users (id, name) VALUES (1, 'Ann');                       -- MySQL

Two traps.

  1. You need a UNIQUE or PRIMARY KEY on the conflict column(s) — ON CONFLICT/ON DUPLICATE KEY fire on a unique-constraint violation, not on an arbitrary WHERE.
  2. MySQL’s old VALUES() function is deprecated (8.0.20+). Use the AS new row alias shown above instead of VALUES(name).

← All recipes