Tuesday, September 27, 2005

INSERT ... ON DUPLICATE KEY UPDATE

At the moment, I'm preparing for the core certification exam. Although I thought that I knew almost everything about the new features in MySQL 4.1, I sometimes find some fantastic "goodies" while I'm learning for the exam. One of them is the INSERT ... ON DUPLICATE KEY UPDATE command.

This command is ideal for logging. You can insert into a table, but do an update, if the row (based on the primary key) already exists. An example might explain this best:
mysql> CREATE TABLE logdata (
-> id INT UNSIGNED NOT NULL,
-> count INT NOT NULL,
-> PRIMARY KEY (id)) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO logdata (id, count)
-> VALUES (1, 1)
-> ON DUPLICATE KEY UPDATE
-> count = count + 1;
Query OK, 1 row affected (0.02 sec)

mysql> INSERT INTO logdata (id, count)
-> VALUES (2, 1)
-> ON DUPLICATE KEY UPDATE
-> count = count + 1;
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO logdata (id, count)
-> VALUES (1, 1)
-> ON DUPLICATE KEY UPDATE
-> count = count + 1;
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM logdata;
+----+-------+
| id | count |
+----+-------+
| 1 | 2 |
| 2 | 1 |
+----+-------+
2 rows in set (0.00 sec)

mysql>

First, the key values 1 and 2 have been inserted. Then we inserted the value 1 again, the command executed an UPDATE instead incrementing the count value to 2.

2 comments:

UltraVi01 said...

Nice Post! Could you kindly help me with a problem. I have this query: "SELECT *, COUNT(term) AS count_term FROM logs GROUP BY term HAVING count_term>1 AND term NOT LIKE 'Enter%' AND term NOT LIKE '' ORDER BY count_term DESC LIMIT 10"

It works great -- It displays the count for each term in the table. However, I added a field called "count" and I would also like to update all of the rows currently in the table with their count. Any help would be Greatly appreciated.
Best,
Ryan

Markus Popp said...

Hi,

If it's a one-time task, I think the easiest way is to materialize the query and temporarily create a table which you use to join to the logs table and update it with the count, like this:

CREATE TABLE logs_cnt
SELECT *, COUNT(term) AS count_term FROM logs GROUP BY term HAVING count_term>1 AND term NOT LIKE 'Enter%' AND term NOT LIKE '' ORDER BY count_term DESC;

ALTER TABLE logs_cnt ADD PRIMARY KEY(term);

UPDATE logs a INNER JOIN logs_cnt b USING (term)
SET a.count = b.count_term;

DROP TABLE logs_cnt;

I hope that helps ;).

Markus