INSERT ON DUPLICATE KEY UPDATE

INSERT ... ON DUPLICATE KEY UPDATE intenta insertar una fila y, si la inserción viola una clave PRIMARY KEY o UNIQUE, actualiza la fila existente en su lugar. Es lo que se conoce como operación upsert (insert + update): inserta si no existe, actualiza si ya existe.

Sintaxis

INSERT INTO tabla (columna1, columna2, ...)
VALUES (valor1, valor2, ...)
ON DUPLICATE KEY UPDATE
    columna2 = valor_nuevo,
    columna3 = valor_nuevo;

La parte ON DUPLICATE KEY UPDATE solo se ejecuta cuando la inserción falla por una clave duplicada. Puedes actualizar una o más columnas en la fila existente.

Ejemplo básico con etiquetas

La tabla etiquetas_producto tiene una restricción UNIQUE compuesta sobre (producto_id, etiqueta). Si intentamos insertar una etiqueta que ya existe para un producto, podemos manejar el duplicado:

INSERT INTO etiquetas_producto (producto_id, etiqueta)
VALUES (1, 'premium')
ON DUPLICATE KEY UPDATE
    etiqueta = 'premium';
Query OK, 0 rows affected (0.00 sec)

El iPhone 15 Pro (producto 1) ya tiene la etiqueta 'premium'. La inserción detecta el duplicado y ejecuta el UPDATE, que en este caso no cambia nada (establece el mismo valor). MySQL reporta "0 rows affected" porque el valor no cambió realmente.

MySQL reporta 1 fila afectada cuando es una nueva inserción, y 2 filas afectadas cuando se encontró un duplicado y se actualizó el valor (cuenta el intento de inserción y la actualización).

Contador de visitas

Un caso de uso clásico: una tabla que registra cuántas veces se ha visitado cada producto. Si el producto no tiene registro, se crea. Si ya tiene, se incrementa el contador:

CREATE TABLE visitas_producto (
    producto_id INT PRIMARY KEY,
    visitas INT NOT NULL DEFAULT 0,
    ultima_visita DATETIME
);
 
INSERT INTO visitas_producto (producto_id, visitas, ultima_visita)
VALUES (1, 1, NOW())
ON DUPLICATE KEY UPDATE
    visitas = visitas + 1,
    ultima_visita = NOW();

La primera vez que se ejecuta para el producto 1, se crea el registro con 1 visita. Las siguientes veces, el UPDATE incrementa el contador y actualiza la fecha. Todo en una sola sentencia, sin necesidad de comprobar primero si el registro existe.

Ejecuta la sentencia 3 veces y después consulta:

SELECT * FROM visitas_producto WHERE producto_id = 1;
producto_idvisitasultima_visita
132025-12-15 10:30:00

Referenciar los valores insertados

Puedes acceder a los valores que se intentaron insertar usando un alias con AS. Esto es útil cuando el valor del UPDATE depende de lo que se intentó insertar:

-- MySQL 8.0.20+
INSERT INTO visitas_producto (producto_id, visitas, ultima_visita)
VALUES (2, 5, '2025-12-20 14:00:00') AS nueva
ON DUPLICATE KEY UPDATE
    visitas = visitas + nueva.visitas,
    ultima_visita = nueva.ultima_visita;

El alias nueva permite acceder a los valores que se intentaron insertar. Si el producto 2 ya tenía 3 visitas, después de esta sentencia tendrá 8 (3 + 5).

En versiones anteriores a MySQL 8.0.20, se usa la función VALUES():

-- MySQL 8.0.19 y anteriores
INSERT INTO visitas_producto (producto_id, visitas, ultima_visita)
VALUES (2, 5, '2025-12-20 14:00:00')
ON DUPLICATE KEY UPDATE
    visitas = visitas + VALUES(visitas),
    ultima_visita = VALUES(ultima_visita);

Actualizar stock al recibir envío

Supongamos que recibes un envío y necesitas actualizar el stock. Si el producto existe, sumas al stock actual:

INSERT INTO productos (id, nombre, precio, stock, categoria_id)
VALUES (1, 'iPhone 15 Pro', 1299.99, 20, 6)
ON DUPLICATE KEY UPDATE
    stock = stock + 20;
Query OK, 2 rows affected (0.01 sec)

El producto 1 ya existe, así que se ejecuta el UPDATE que suma 20 unidades al stock actual (45 + 20 = 65). El iPhone 15 Pro pasa de 45 a 65 unidades en stock.

Con múltiples filas

Puedes combinar la inserción múltiple con ON DUPLICATE KEY UPDATE:

INSERT INTO etiquetas_producto (producto_id, etiqueta) VALUES
    (5, 'premium'),
    (5, 'ultraligero'),
    (6, 'portátil empresarial')
ON DUPLICATE KEY UPDATE
    etiqueta = VALUES(etiqueta);

Cada fila se evalúa individualmente. El MacBook Air M3 (producto 5) ya tiene la etiqueta 'premium', así que esa fila se actualiza sin cambio real. Las otras etiquetas ('ultraligero' para el MacBook y 'portátil empresarial' para el ThinkPad) son nuevas y se insertan normalmente.

ON DUPLICATE KEY vs INSERT IGNORE

Ambas manejan duplicados, pero de forma diferente:

INSERT IGNORE descarta la fila duplicada sin hacer nada. ON DUPLICATE KEY UPDATE ejecuta una actualización sobre la fila existente. Usa INSERT IGNORE cuando quieres que los duplicados se ignoren silenciosamente. Usa ON DUPLICATE KEY UPDATE cuando quieres actualizar datos en caso de conflicto.

-- INSERT IGNORE: si el email existe, no hace nada
INSERT IGNORE INTO clientes (nombre, apellidos, email)
VALUES ('María', 'García López', 'maria.garcia@email.com');
 
-- ON DUPLICATE KEY: si el email existe, actualiza el teléfono
INSERT INTO clientes (nombre, apellidos, email, telefono)
VALUES ('María', 'García López', 'maria.garcia@email.com', '600000000')
ON DUPLICATE KEY UPDATE
    telefono = '600000000';

El primer caso simplemente ignora el duplicado. El segundo aprovecha el conflicto para actualizar el teléfono de María a un nuevo número.

Limpiar

DROP TABLE IF EXISTS visitas_producto;

En el siguiente artículo veremos cómo insertar valores de fecha correctamente en MySQL, un tema que suele generar confusión cuando no se conocen los formatos esperados.

Escrito por Eduardo Lázaro