Upsert in SQL
Upsert is an operation that ➊ inserts new information into the database and ➋ updates present ones. Let’s have a look at the way it works in several DBMS. The examples are interactive, so you may learn and observe.
We are going to use the toy staff
desk:
┌────┬───────┬────────┬────────────┬────────┐
│ id │ title │ metropolis │ division │ wage │
├────┼───────┼────────┼────────────┼────────┤
│ 11 │ Diane │ London │ hr │ 70 │
│ 12 │ Bob │ London │ hr │ 78 │
│ 21 │ Emma │ London │ it │ 84 │
│ 22 │ Grace │ Berlin │ it │ 90 │
│ 23 │ Henry │ London │ it │ 104 │
│ 24 │ Irene │ Berlin │ it │ 104 │
│ 31 │ Cindy │ Berlin │ gross sales │ 96 │
│ 32 │ Dave │ London │ gross sales │ 96 │
└────┴───────┴────────┴────────────┴────────┘
As an example we’re including two new staff:
(25, 'Frank', 'Berlin', 'it', 120)
(33, 'Alice', 'Berlin', 'gross sales', 100)
And updating the opposite two:
- (11, 'Diane', 'London', 'hr', 70)
+ (11, 'Diane', 'Berlin', 'hr', 70)
- (21, 'Emma', 'London', 'it', 84)
+ (21, 'Emma', 'London', 'it', 95)
You’ll be able to learn this text from begin to end, or you may skip to a piece concerning the particular database engine:
MySQL/MariaDB •
SQLite •
PostgreSQL •
SQL Server •
Oracle •
Others
INSERT
Let’s strive the straightforward means:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100);
As anticipated, it fails for Diane and Emma: the worker ID is a major key, so we will not have two staff with the identical ID. We’ll should strive one thing else.
MySQL: REPLACE
MySQL’s change into
inserts information with new major/distinctive keys (in our case, worker IDs) and replaces those with present keys:
change into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100);
The dangerous factor about change
is that it does an precise delete
adopted by insert
, which has some sudden side effects like firing on delete
triggers.
Additionally, you may’t reference the outdated values when changing the report. For instance, you may’t set metropolis
= Berlin (prev. London)
for Diane.
To disregard the duplicates and solely carry out inserts, you need to use insert ignore
:
insert ignore into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100);
change
might be not one of the simplest ways to do upserts in MySQL. There’s a higher different ↓
MySQL: INSERT…ON DUPLICATE KEY UPDATE
MySQL’s on duplicate key replace
inserts information with new major/distinctive keys (in our case, worker IDs) and updates those with present keys:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
on duplicate key replace
metropolis = values(metropolis),
wage = values(wage);
When updating a report, you may reference the outdated worth by column title (metropolis
) and the brand new worth with the values()
perform (values(metropolis)
). Now we are able to truly replace a worth, not simply change it:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70)
on duplicate key replace
metropolis = concat(values(metropolis), ' (prev. ', metropolis, ')');
Beginning with MySQL 8.0, you may alias the brand new values and never use the ugly values()
perform:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
as new
on duplicate key replace
metropolis = new.metropolis,
wage = new.wage;
SQLite: INSERT OR…
SQLite’s insert or change
inserts information with new major/distinctive keys (in our case, worker IDs) and replaces those with present keys:
insert or change into staff
(id, title, metropolis, division, wage)
values
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100),
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95);
Like MySQL’s change
, SQLite’s or change
deletes present duplicate information earlier than inserting the brand new ones. It fires delete triggers in some circumstances and doesn’t permit references to outdated values.
SQLite gives quite a lot of different methods as an alternative of change
:
ignore
: skips the duplicate information (just like MySQL’signore
).rollback
: aborts the present assertion and rolls again your entire transaction.abort
: aborts the present assertion and reverts any adjustments made by it, however doesn’t roll again the transaction.fail
: aborts the present assertion, however doesn’t revert any adjustments already made by it, and doesn’t roll again the transaction.
insert or abort into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100);
insert or change
might be not one of the simplest ways to do upserts in SQLite. There’s a higher different ↓
SQLite: INSERT…ON CONFLICT
SQLite’s on battle do replace
inserts information with new major/distinctive keys (in our case, worker IDs) and updates those with present keys:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
on battle do replace set
metropolis = excluded.metropolis,
wage = excluded.wage;
When updating a report, you may reference the outdated worth by column title (metropolis
) and the brand new worth with the excluded
prefix (excluded.metropolis
).
You’ll be able to optionally disable some updates utilizing the the place
clause:
insert into staff
(id, title, metropolis, division, wage)
values
(21, 'Emma', 'London', 'it', 95),
(22, 'Grace', 'Berlin', 'it', 105)
on battle do replace set
wage = excluded.wage the place excluded.wage < 100;
Apart from do replace
, there’s additionally do nothing
, which ignores conflicts and solely inserts new information:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
on battle do nothing;
on battle
even permits you to specify completely different guidelines for various uniqueness constraints:
create distinctive index idx_employees_name on staff(title);
insert into staff
(id, title, metropolis, division, wage)
values
-- enhance Emma's wage to 95
(21, 'Emma', 'London', 'it', 95),
-- change Grace's title to Diane
(22, 'Diane', 'Berlin', 'it', 90)
on battle (id) do replace set
metropolis = excluded.metropolis,
wage = excluded.wage
on battle (title) do nothing;
Code playgrounds
Interactive examples on this publish are powered by codapi — the platform I am constructing. Use it to embed code playgrounds into your on-line course, documentation, or weblog.
PostgreSQL: INSERT…ON CONFLICT
PostgreSQL’s on battle do replace
is similar to SQLite’s (in reality, SQLite’s implementation was impressed by PostgreSQL’s). It inserts information with new major/distinctive keys (in our case, worker IDs) and updates those with present keys:
insert into staff
(id, title, metropolis, division, wage)
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
on battle (id) do replace set
metropolis = excluded.metropolis,
wage = excluded.wage;
When updating a report, you may reference the outdated worth by column title (metropolis
) and the brand new worth with the excluded
prefix (excluded.metropolis
).
Notice that the constraint reference after on battle
(it’s known as battle goal — id
in our case) is required. You may as well specify a constraint title:
alter desk staff add constraint uniq_employees_name distinctive (title);
insert into staff
(id, title, metropolis, division, wage)
values
-- change Grace's title to Diane
(22, 'Diane', 'Berlin', 'it', 90)
on battle on constraint uniq_employees_name do nothing;
As you may see from the instance above, PostgreSQL helps do nothing
identical to SQLite. It additionally helps the the place
clause:
insert into staff
(id, title, metropolis, division, wage)
values
(21, 'Emma', 'London', 'it', 95),
(22, 'Grace', 'Berlin', 'it', 105)
on battle (id) do replace set
wage = excluded.wage the place excluded.wage < 100;
In contrast to SQLite, PostgreSQL doesn’t permit a number of on battle
clauses:
create distinctive index idx_employees_name on staff(title);
insert into staff
(id, title, metropolis, division, wage)
values
(21, 'Emma', 'London', 'it', 95),
(22, 'Diane', 'Berlin', 'it', 90)
on battle (id) do replace set
metropolis = excluded.metropolis,
wage = excluded.wage
on battle (title) do nothing;
insert...on battle
is a superb approach to do upserts on older PostgreSQL variations. For contemporary ones (15+) there’s a higher different ↓
SQL Commonplace: MERGE
Now that we have seen all of the artistic methods distributors have applied upsert of their programs, let us take a look at the customary means — the merge
assertion:
merge into staff
utilizing (
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
) as new (id, title, metropolis, division, wage)
on staff.id = new.id
when matched then replace set
metropolis = new.metropolis,
wage = new.wage
when not matched then insert
values (id, title, metropolis, division, wage);
Here’s what occurred:
- We specified the merge supply within the
utilizing
clause (right here we use thevalues
because the supply, howeverutilizing
additionally accepts tables, views and subqueries). - We gave the merge supply an alias (
new
) for future reference. - We outlined the match standards between the merge supply (our
new
values) and the merge goal (thestaff
desk) utilizing theon
clause (staff.id = new.id
). - We set the motion to take when the merge supply and merge goal match by the given standards (replace
metropolis
andwage
with new values). - We set the motion to take when the merge supply and merge goal do not match (i.e., there are new information within the supply, so we should always insert them).
Whew. That is rather a lot to course of. However the merge
assertion can do far more. For instance, you may change the inserted values:
merge into staff
utilizing (
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
) as new (id, title, metropolis, division, wage)
on staff.id = new.id
when matched then replace set
metropolis = new.metropolis,
wage = new.wage
when not matched then insert
values (id, title || ' (new!)', metropolis, division, wage);
And even delete the matching information:
merge into staff
utilizing (
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
) as new (id, title, metropolis, division, wage)
on staff.id = new.id
when matched then delete
when not matched then insert
values (id, title, metropolis, division, wage);
Or ignore new information altogether by omitting the when not matched
:
merge into staff
utilizing (
values
(11, 'Diane', 'Berlin', 'hr', 70),
(21, 'Emma', 'London', 'it', 95),
(25, 'Frank', 'Berlin', 'it', 120),
(33, 'Alice', 'Berlin', 'gross sales', 100)
) as new (id, title, metropolis, division, wage)
on staff.id = new.id
when matched then replace set
metropolis = new.metropolis,
wage = new.wage;
You’ll be able to carry out conditional inserts, updates and deletes utilizing when...and
clause:
merge into staff
utilizing (
values
(21, 'Emma', 'London', 'it', 95),
(22, 'Grace', 'Berlin', 'it', 105)
) as new (id, title, metropolis, division, wage)
on staff.id = new.id
when matched and new.wage < 100 then replace set
metropolis = new.metropolis,
wage = new.wage;
merge
is supported in PostgreSQL (15+), SQL Server (2008+) and Oracle (11+). In fact, every of them has its personal quirks. For instance, PostgreSQL provides the non-standard do nothing
motion, whereas SQL Server helps completely different when not matched
actions relying on the place the worth is lacking — in a supply or goal dataset.
→ Documentation:
Standard •
PostgreSQL •
SQL Server •
Oracle
Different database engines
BigQuery, H2, Snowflake, DB2 — all assist merge
(every to some extent).
MariaDB helps replace
and on duplicate key update
, identical to MySQL.
Clickhouse doesn’t have upsert SQL statements, nevertheless it does present a ReplacingMergeTree desk engine that routinely performs a replace-style upsert on insert.
Cockroachdb helps insert...on conflict
(like PostgreSQL), however with an optionally available battle goal. It additionally gives an upsert
assertion (ta-da!) that works like an computerized on battle do replace
.
DuckDB helps insert or replace
and insert or ignore
(like SQLite) in addition to insert...on conflict
(additionally like SQLite), however with out a number of clauses.
Abstract
We have reviewed methods to do upserts in every of the main databases:
change
oron duplicate key replace
in MySQL (MariaDB).insert or change
andon battle do
in SQLite.on battle do
in older PostgreSQL variations.merge
in trendy PostgreSQL, SQL Server and Oracle.
It is humorous that (nearly) no DBMS has truly named the assertion upsert
, regardless of the recognition of the title.
──
P.S. Excited about mastering superior SQL? Try my guide — SQL Window Functions Explained
Subscribe
to maintain up with new posts.