“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.
- You need a UNIQUE or PRIMARY KEY on the conflict column(s) —
ON CONFLICT/ON DUPLICATE KEYfire on a unique-constraint violation, not on an arbitraryWHERE.- MySQL’s old
VALUES()function is deprecated (8.0.20+). Use theAS newrow alias shown above instead ofVALUES(name).