Upserts

Upserts with ON DUPLICATE KEY UPDATE

An upsert inserts a row or, if the primary key or a UNIQUE index already holds its value, updates the existing row. A stock delivery keyed on the unique sku is the classic case:

A stock delivery as an upsertSQL
INSERT INTO products (sku, title, category_id, price, stock)
VALUES ('BK-SQL-01', 'SQL Queries That Scale', 3, 44.50, 20),
       ('BK-PHP-02', 'PHP 8.5 Pocket Guide',   2, 19.90, 30) AS new
ON DUPLICATE KEY UPDATE stock = products.stock + new.stock, price = new.price;
INSERT INTO products (sku, title, category_id, price, stock)
VALUES ('BK-PHP-03', 'Testing with Pest', 2, 27.00, 15);
SELECT id, sku, price, stock FROM products WHERE id > 8 OR sku = 'BK-SQL-01';
Output
Query OK, 3 rows affected (0.015 sec)
Records: 2  Duplicates: 1  Warnings: 0
Query OK, 1 row affected (0.008 sec)
+----+-----------+-------+-------+
| id | sku       | price | stock |
+----+-----------+-------+-------+
|  2 | BK-SQL-01 | 44.50 |    32 |
|  9 | BK-PHP-02 | 19.90 |    30 |
| 11 | BK-PHP-03 | 27.00 |    15 |
+----+-----------+-------+-------+
3 rows in set (0.001 sec)

Each row counts 1 if inserted, 2 if updated and 0 if nothing changed, so 3 is one update (stock 12 to 32) plus one insert. AS new is a row alias (MySQL 8.0.19 524 +): new.stock is the incoming value, products.stock the stored one. The older VALUES(stock) spelling is deprecated since 8.0.20 and now draws warning 1287.

Note the ids: the upsert reserved an AUTO_INCREMENT value for the row that became an update, so the next insert got 11, not 10. InnoDB never reuses such values; never treat ids as a gapless sequence.