Himanshu SharmaSoftware engineer
Let’s talk

MySQL · Lesson 15 of 50

Upserts: ON DUPLICATE KEY, IGNORE and REPLACE

Insert a row or update it if it already exists using ON DUPLICATE KEY UPDATE, skip duplicates with INSERT IGNORE, and learn why REPLACE can be risky.

12 minChanging dataUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonChoose between INSERT IGNORE, INSERT ... ON DUPLICATE KEY UPDATE and REPLACE when a row might already exist.

When the row might already exist

A normal INSERT fails if it would create a duplicate primary key or unique value. Often that is what you want. But sometimes the question is "make sure this row exists with these values", for example syncing product data from a supplier feed, counting page views, or saving user settings. Checking first with a SELECT and then inserting or updating is slow, and two processes can race each other. MySQL gives you single-statement answers.

INSERT IGNORE: skip duplicates

sql
INSERT IGNORE INTO categories (id, name) VALUES (1, 'Gadgets'), (6, 'Garden');
sql
SELECT * FROM categories;
result
+----+-------------+
| id | name        |
+----+-------------+
|  1 | Electronics |
|  2 | Books       |
|  3 | Home        |
|  4 | Sports      |
|  5 | Toys        |
|  6 | Garden      |
+----+-------------+

Category 1 already existed, so that row was skipped and its name is still Electronics. The new category 6 was inserted. The statement produced a warning, not an error, for the duplicate:

sql
SHOW WARNINGS;

The catch: IGNORE turns many other errors into warnings too, such as text being cut to fit a column. Use it only when skipping duplicates is exactly what you want, and prefer an explicit ON DUPLICATE KEY UPDATE otherwise.

ON DUPLICATE KEY UPDATE: the upsert

This clause says "if the insert would collide with a primary or unique key, update the existing row instead". Here a supplier delivers 40 more units of product 1, and a new product 16:

sql
INSERT INTO products (id, name, category_id, price, stock)
VALUES (1, 'Wireless Mouse', 1, 24.99, 40), (16, 'Travel Adapter', 1, 15.00, 100)
AS new
ON DUPLICATE KEY UPDATE stock = products.stock + new.stock;
SELECT ROW_COUNT() AS reported;
result
+----------+
| reported |
+----------+
|        3 |
+----------+
sql
SELECT id, name, stock FROM products WHERE id IN (1, 16);
result
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | Wireless Mouse |   190 |
| 16 | Travel Adapter |   100 |
+----+----------------+-------+

Product 1 existed, so its stock went from 150 to 190. Product 16 did not, so it was inserted with stock 100.

The AS new part gives the incoming row a name so you can refer to its values as new.stock. This alias form works from MySQL 8.0.19. Older code uses the function VALUES(stock) instead, which still works but is deprecated.

The ROW_COUNT() call right after the statement reports how many rows were affected. It counts 1 for each inserted row, 2 for each row changed by the update part, and 0 for an existing row whose values did not change. Here that adds up to 3: two for product 1 and one for product 16. Call it straight after the statement, because any other statement, even a SELECT, resets it.

Note that the table needs a unique key for this to be meaningful. If id were not a primary key or unique, there would never be a duplicate and every statement would insert.

A counter example

Upserts are perfect for counters. Create a small table and count visits:

sql
CREATE TABLE page_views (page VARCHAR(50) PRIMARY KEY, views INT NOT NULL);
INSERT INTO page_views (page, views) VALUES ('home', 1) AS new ON DUPLICATE KEY UPDATE views = page_views.views + 1;
INSERT INTO page_views (page, views) VALUES ('home', 1) AS new ON DUPLICATE KEY UPDATE views = page_views.views + 1;
INSERT INTO page_views (page, views) VALUES ('about', 1) AS new ON DUPLICATE KEY UPDATE views = page_views.views + 1;
SELECT * FROM page_views ORDER BY page;
result
+-------+-------+
| page  | views |
+-------+-------+
| about |     1 |
| home  |     2 |
+-------+-------+

The same statement works whether or not the row exists yet, and increments atomically, so many visitors at once cannot lose a count.

REPLACE: delete and insert

REPLACE works like INSERT, but if the key exists it first deletes the old row, then inserts the new one. It looks convenient, but it has traps. Columns you do not mention go back to their defaults instead of keeping their old values. Here Paula's manager and department are lost, because the REPLACE did not mention them:

sql
SELECT id, name, manager_id, department_id FROM employees WHERE id = 5;
REPLACE INTO employees (id, name, job_title, salary, hire_date)
VALUES (5, 'Paula Gomez', 'Developer', 85000.00, '2021-04-12');
SELECT id, name, manager_id, department_id FROM employees WHERE id = 5;
result
+----+-------------+------------+---------------+
| id | name        | manager_id | department_id |
+----+-------------+------------+---------------+
|  5 | Paula Gomez |          3 |             1 |
+----+-------------+------------+---------------+
+----+-------------+------------+---------------+
| id | name        | manager_id | department_id |
+----+-------------+------------+---------------+
|  5 | Paula Gomez |       NULL |          NULL |
+----+-------------+------------+---------------+

The delete half also runs into foreign keys. Every category has products pointing at it, so MySQL refuses to replace one:

sql
REPLACE INTO categories (id, name) VALUES (5, 'Toys and Games');
result
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`shop`.`products`, CONSTRAINT `products_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`))

REPLACE can also fire delete triggers and always uses up a new AUTO_INCREMENT value. For these reasons, INSERT ... ON DUPLICATE KEY UPDATE is the safer habit: it changes only the columns you name.

Which one to use

Goal Use
Insert only if new, ignore the rest INSERT IGNORE (or better, check specific errors)
Insert, or change some columns when it exists INSERT ... ON DUPLICATE KEY UPDATE
Replace the whole row, accepting delete plus insert REPLACE

Common mistakes

  • Using INSERT IGNORE everywhere and hiding real data problems.
  • Using REPLACE for partial updates, which resets omitted columns.
  • Forgetting that a unique key is required for an upsert to detect duplicates.
  • Updating the key itself inside ON DUPLICATE KEY UPDATE.
  • Mixing up old and new values in the update expression. products.stock is the existing row; new.stock is the incoming one.

Exercise

A feed delivers product 2 (Mechanical Keyboard) with a new price of 84.00, and a new product 17, Laptop Stand, category 1, price 35.00, stock 60. Write one upsert statement that updates the price of the existing product only and inserts the new one.

Show one possible answer
sql
INSERT INTO products (id, name, category_id, price, stock)
VALUES (2, 'Mechanical Keyboard', 1, 84.00, 60), (17, 'Laptop Stand', 1, 35.00, 60)
AS new
ON DUPLICATE KEY UPDATE price = new.price;
SELECT id, name, price, stock FROM products WHERE id IN (2, 17);
result
+----+---------------------+-------+-------+
| id | name                | price | stock |
+----+---------------------+-------+-------+
|  2 | Mechanical Keyboard | 84.00 |    60 |
| 17 | Laptop Stand        | 35.00 |    60 |
+----+---------------------+-------+-------+

Product 2 keeps its stock of 60 and gets the new price; product 17 is inserted.

Key points

  • A duplicate primary or unique key makes a plain INSERT fail. Three MySQL options handle that case: IGNORE, ON DUPLICATE KEY UPDATE and REPLACE.
  • INSERT IGNORE silently skips the conflicting row, and also downgrades other errors to warnings, so use it carefully.
  • INSERT ... ON DUPLICATE KEY UPDATE is the standard upsert: insert if new, otherwise update chosen columns.
  • REPLACE deletes the old row and inserts a new one, which can trigger deletes and break foreign keys. Prefer ON DUPLICATE KEY UPDATE.

Check yourself

1. What is an upsert?
2. What does INSERT IGNORE do when the primary key already exists?
3. Why can REPLACE be dangerous?
4. Which clause updates stock by adding the new quantity when the product id exists?