SQL Tutorial › Modifying Data › Upsert & REPLACE

SQL Upsert: INSERT ON DUPLICATE KEY UPDATE and REPLACE

IntermediateLesson 21 of 6910 min read5 queries run on MySQL 8.4

An upsert means “insert this row, or update it if it already exists”. Syncing a product feed, counting page views and saving user settings all need it. MySQL offers three tools, INSERT ... ON DUPLICATE KEY UPDATE, INSERT IGNORE and REPLACE, and they behave very differently when a row already exists.

Advertisement
In this lesson
  • Upsert with INSERT ... ON DUPLICATE KEY UPDATE
  • Refer to the new values with a row alias
  • Skip duplicates with INSERT IGNORE
  • Why REPLACE deletes before it inserts

What counts as a duplicate?

All three tools react to a clash on a PRIMARY KEY or UNIQUE index. Without such a key there is no “existing row” to detect, and every insert simply adds a row.

products · 9 rows
product_idproduct_namecategorypricestock
1Wireless MouseElectronics799120
2Mechanical KeyboardElectronics349945
3USB-C HubElectronics19990
4Office ChairFurniture899912
5Standing DeskFurniture189995
6Notebook PackStationery249300
7Gel PensStationery199250
8Desk LampFurniture149930
9Monitor ArmFurniture279918

INSERT … ON DUPLICATE KEY UPDATE

A supplier sends a price list. Product 2 exists (update it), product 10 is new (insert it):

MySQL
INSERT INTO products (product_id, product_name, category, price, stock)VALUES (2, 'Mechanical Keyboard', 'Electronics', 3299, 60),       (10, 'Webcam HD', 'Electronics', 2499, 40) AS newON DUPLICATE KEY UPDATE  price = new.price,  stock = new.stock; SELECT product_id, product_name, price, stock FROM products WHERE product_id IN (2, 10);
ResultMySQL 8.4
mysql> INSERT INTO products (product_id, product_name, category, price, stock)
Query OK, 3 rows affected
mysql> SELECT product_id, product_name, price, stock FROM products WHERE prod…
product_idproduct_namepricestock
2Mechanical Keyboard329960
10Webcam HD249940
2 rows in set
How this query works, step by step
  1. MySQL tries to insert each row.
  2. Product 2 clashes with the existing primary key, so instead of failing, MySQL runs the UPDATE part on that existing row.
  3. AS new is a row alias: new.price means “the price from the row I tried to insert”.
  4. Product 10 has no clash, so it is inserted normally.
  5. MySQL reports 3 rows affected: it counts 1 for each inserted row and 2 for each updated row.
Note

Older code writes price = VALUES(price). That form still works but is deprecated since MySQL 8.0.20; use the row alias.

Counters with ON DUPLICATE KEY

A classic use: count events per day without checking first whether the row exists:

MySQL
CREATE TABLE page_views (page VARCHAR(40) PRIMARY KEY, views INT NOT NULL);INSERT INTO page_views VALUES ('sql-tutorial', 1) ON DUPLICATE KEY UPDATE views = views + 1;INSERT INTO page_views VALUES ('sql-tutorial', 1) ON DUPLICATE KEY UPDATE views = views + 1;INSERT INTO page_views VALUES ('sql-joins', 1)    ON DUPLICATE KEY UPDATE views = views + 1;SELECT * FROM page_views;
ResultMySQL 8.4
mysql> CREATE TABLE page_views (page VARCHAR(40) PRIMARY KEY, views INT NOT N…
Query OK, 0 rows affected
mysql> INSERT INTO page_views VALUES ('sql-tutorial', 1) ON DUPLICATE KEY UPD…
Query OK, 1 row affected
mysql> INSERT INTO page_views VALUES ('sql-tutorial', 1) ON DUPLICATE KEY UPD…
Query OK, 2 rows affected
mysql> INSERT INTO page_views VALUES ('sql-joins', 1) ON DUPLICATE KEY UPD…
Query OK, 1 row affected
mysql> SELECT * FROM page_views
pageviews
sql-joins1
sql-tutorial2
2 rows in set
How this query works, step by step
  1. The first insert creates sql-tutorial with 1 view.
  2. The second clashes, so views = views + 1 makes it 2.
  3. sql-joins is new and starts at 1.
  4. Each statement is a single atomic operation, so two users hitting the page at the same moment cannot lose a count.

INSERT IGNORE

INSERT IGNORE silently skips rows that would cause a duplicate-key error and inserts the rest:

MySQL
INSERT IGNORE INTO departments (dept_id, dept_name, location)VALUES (1, 'Engineering', 'Chennai'),       (6, 'Legal', 'Delhi');SHOW WARNINGS;SELECT * FROM departments;
ResultMySQL 8.4
mysql> INSERT IGNORE INTO departments (dept_id, dept_name, location)
Query OK, 1 row affected, 1 warning
mysql> SHOW WARNINGS
LevelCodeMessage
Warning1062Duplicate entry '1' for key 'departments.PRIMARY'
1 row in set
mysql> SELECT * FROM departments
dept_iddept_namelocation
1EngineeringBengaluru
2SalesMumbai
3MarketingPune
4HRDelhi
5FinanceMumbai
6LegalDelhi
6 rows in set
How this query works, step by step
  1. Department 1 already exists, so that row is skipped and becomes a warning; its location stays Bengaluru.
  2. Department 6 is new and is inserted: 1 row affected, 1 warning.
  3. SHOW WARNINGS explains which row was ignored.
Watch out

IGNORE also turns other errors into warnings, such as values that are too long (they are cut short). Use it only when you really want “insert if new, otherwise do nothing”.

REPLACE INTO

REPLACE works like INSERT, but if the key exists it deletes the old row and inserts the new one:

MySQL
REPLACE INTO customers (customer_id, customer_name, city)VALUES (6, 'Manoj Pillai', 'Kochi'); SELECT * FROM customers WHERE customer_id = 6;
ResultMySQL 8.4
mysql> REPLACE INTO customers (customer_id, customer_name, city)
Query OK, 2 rows affected
mysql> SELECT * FROM customers WHERE customer_id = 6
customer_idcustomer_nameemailcityjoined_on
6Manoj PillaiNULLKochiNULL
1 row in set
How this query works, step by step
  1. Customer 6 exists, so MySQL deletes that row and inserts the new one: 2 rows affected.
  2. The new row has only the columns given. email and joined_on were not supplied, so the old values are lost and become NULL.

REPLACE fails if other tables reference the row through a foreign key, because it really deletes it. Customer 1 has orders:

MySQL
REPLACE INTO customers (customer_id, customer_name, city) VALUES (1, 'Ananya Sharma', 'Pune');
ResultMySQL 8.4
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`company`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`customer_id`))

Which one should I use?

ToolRow is newRow existsTypical use
INSERT ... ON DUPLICATE KEY UPDATEinsertupdate chosen columnssync, counters, settings
INSERT IGNOREinsertdo nothing (warning)load data, skip duplicates
REPLACEinsertdelete + insertrarely; full overwrite of a standalone table

In other databases the same idea is INSERT ... ON CONFLICT DO UPDATE (PostgreSQL, SQLite) or MERGE (SQL Server, Oracle).

Exercise: Upsert stock levels

Products 3 and 11 arrive: product 3 (USB-C Hub, Electronics, 1999) with 30 units, product 11 (Sticky Notes, Stationery, 99) with 500 units. Write one statement that inserts new products and, for existing ones, sets stock to the new value. Show both products.

Expected resultMySQL 8.4
mysql> INSERT INTO products (product_id, product_name, category, price, stock)
Query OK, 3 rows affected
mysql> SELECT product_id, product_name, stock FROM products WHERE product_id …
product_idproduct_namestock
3USB-C Hub30
11Sticky Notes500
2 rows in set
Hint 1

Use a row alias and update only stock.

Quick quiz

1. What triggers the UPDATE part of ON DUPLICATE KEY UPDATE?

2. How many rows does MySQL report as affected when ON DUPLICATE KEY UPDATE changes one existing row?

3. What happens to columns you do not list in a REPLACE?

Upsert FAQ

How do I insert or update in MySQL?

Use INSERT INTO t (...) VALUES (...) AS new ON DUPLICATE KEY UPDATE col = new.col; It inserts new rows and updates rows whose primary or unique key already exists.

What is the difference between REPLACE and INSERT ON DUPLICATE KEY UPDATE?

REPLACE deletes the existing row and inserts a new one, losing unlisted columns and firing delete triggers. ON DUPLICATE KEY UPDATE changes only the columns you name in the existing row.

Does MySQL support MERGE?

No. MySQL uses INSERT ... ON DUPLICATE KEY UPDATE instead of the standard MERGE statement.

Is INSERT IGNORE safe?

It is safe for skipping duplicate keys, but it also hides other problems by turning errors into warnings. Check SHOW WARNINGS after using it.

Leave a Comment