TLDR
- SQLite and PostGreSQL: Use
EXCLUDED
to reference the would-have-been inserted row - MySQL: Use
VALUES()
function to reference the would-have-been inserted row
Syntax
-- sqlite
-- psql
INSERT INTO users (email, last_name, first_name)
VALUES
('john.doe@example.com', 'Doe', 'John'),
('jane.doe@example.com', 'Doe', 'Jane')
ON CONFLICT (email) DO UPDATE SET
last_name = EXCLUDED.last_name,
first_name = EXCLUDED.first_name;
-- mysql
INSERT INTO users (email, last_name, first_name)
VALUES
('john.doe@example.com', 'Doe', 'John'),
('jane.doe@example.com', 'Doe', 'Jane')
ON DUPLICATE KEY UPDATE
last_name = VALUES(last_name),
first_name = VALUES(first_name);
Gotchas
Upsert Might not Work Like How You Thought It Would
Per documents, the such ONLY works on columns with an uniqueness constraint, like UNIQUE
or PRIMARY KEY
. And, the upsert flow for each row is decided separately.
Should other qualifiers, like NOT NULL
, fail to pass, the row insertion/update would still fail.
With hindsight, this was already stated in MySQL's "DUPLICATE KEY
but not having to specify which" part.
Unwanted Row Locks
The affected rows will be locked during the query12, even though they might not be updated in the end. Treat them with care.
Anecdote
SQLite follows PostgreSQL in its upsert syntax, and it is documented.