¿Cómo actualizo si existe, inserte si no (AKA "upsert" o "merge") en MySQL?

Respuestas:

191

Uso INSERT ... ON DUPLICATE KEY UPDATE. Por ejemplo:

INSERT INTO `usage`
(`thing_id`, `times_used`, `first_time_used`)
VALUES
(4815162342, 1, NOW())
ON DUPLICATE KEY UPDATE
`times_used` = `times_used` + 1
caos
fuente
10
Sí, creo que se llama el equivalente de SQL Server MERGE. En general, el concepto a menudo se conoce como "UPSERT".
caos
3
@blub: si crea una clave única geby topicfuncionará ( ALTER TABLE table ADD UNIQUE geb_by_topic (geb, topic)).
caos
1
@Brian: también se llama el equivalente de Oracle, MERGEpero no estoy seguro de si su sintaxis es idéntica a la de SQL Server.
Ken Keenan
1
@Brooks: si le pasa un 0, en realidad usará 0 como valor. Entonces no hagas eso. No le pase nada, ni lo pase a NULL, para permitir que el auto_incrementcomportamiento funcione (que de lo contrario, sí, funciona como presume; consulte dev.mysql.com/doc/refman/5.5/en/example-auto-increment. html )
caos
3
Gracias, y puede usar VALUES(col)para obtener el valor del argumento de inserción en caso de duplicado. Por ejemplo:ON DUPLICATE UPDATE b = VALUES(b), c = VALUES(c)
gerrytan
5

Sé que esta es una vieja pregunta, pero Google me llevó aquí recientemente, así que imagino que otros también vendrán.

@chaos es correcto: existe la INSERT ... ON DUPLICATE KEY UPDATEsintaxis.

Sin embargo, la pregunta original fue sobre MySQL específicamente, y en MySQL existe la REPLACE INTO ...sintaxis. En mi humilde opinión, este comando es más fácil y más sencillo de usar para upserts. Del manual:

REPLACE funciona exactamente como INSERT, excepto que si una fila anterior de la tabla tiene el mismo valor que una fila nueva para una CLAVE PRIMARIA o un índice ÚNICO, la fila anterior se elimina antes de insertar la nueva fila.

Tenga en cuenta que esto no es SQL estándar. Un ejemplo del manual:

CREATE TABLE test (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  data VARCHAR(64) DEFAULT NULL,
  ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

mysql> REPLACE INTO test VALUES (1, 'Old', '2014-08-20 18:47:00');
Query OK, 1 row affected (0.04 sec)

mysql> REPLACE INTO test VALUES (1, 'New', '2014-08-20 18:47:42');
Query OK, 2 rows affected (0.04 sec)

mysql> SELECT * FROM test;
+----+------+---------------------+
| id | data | ts                  |
+----+------+---------------------+
|  1 | New  | 2014-08-20 18:47:42 |
+----+------+---------------------+
1 row in set (0.00 sec)

Editar: solo una advertencia justa que REPLACE INTOno es como UPDATE. Como dice el manual, REPLACEelimina la fila si existe, luego inserta una nueva. (Observe las divertidas "2 filas afectadas" en el ejemplo anterior). Es decir, reemplazará los valores de todas las columnas de un registro existente (y no simplemente actualizará algunas columnas). El comportamiento de MySQL REPLACE INTOes muy similar al de Sqlite INSERT OR REPLACE INTO. Consulte esta pregunta para conocer algunas soluciones alternativas si solo desea actualizar algunas columnas (y no todas) si el registro ya existe.

Armadillo Jim
fuente