Modificar datos · conflictos

UPSERT en SQL: ON CONFLICT y ON DUPLICATE KEY UPDATE

Inserta una fila nueva o actualiza la existente cuando una restricción única detecta el mismo identificador, sin confundir el patrón con MERGE.

Lectura: 7–9 minSQL comprobadoPráctica con pistas
Contenido de esta guía

Respuesta rápida

UPSERT es el patrón “inserta si no existe; actualiza si ya existe una fila que entra en conflicto con una clave única”. No hay una sintaxis universal entre motores: PostgreSQL y SQLite usan INSERT ... ON CONFLICT, mientras MySQL ofrece INSERT ... ON DUPLICATE KEY UPDATE.

PostgreSQL / SQLite
INSERT INTO inventario (producto_id, stock)
VALUES (1, 8)
ON CONFLICT (producto_id) DO UPDATE
SET stock = excluded.stock;

La idea central no es “capturar cualquier error”, sino decidir qué hacer ante un conflicto de unicidad concreto.

Modelo mental: una fila propuesta y una fila existente

Un INSERT normal intenta crear una fila. Si una PRIMARY KEY o una regla UNIQUE detecta un duplicado, el motor puede rechazar la operación. Un UPSERT añade una alternativa: ante ese conflicto, actualizar la fila que ya existe o no hacer nada.

Pregunta útil: “si llega de nuevo el mismo identificador lógico, ¿quiero rechazarlo, ignorarlo o reemplazar determinados valores?”. La respuesta define si necesitas un INSERT normal, DO NOTHING o una actualización por conflicto.

Ejemplo comprobado: actualizar el stock si el producto ya existe

Demo ejecutable en SQLite
CREATE TEMP TABLE inventario_upsert_demo (
  producto_id INTEGER PRIMARY KEY,
  stock INTEGER NOT NULL
);

INSERT INTO inventario_upsert_demo (producto_id, stock)
VALUES (1, 5);

INSERT INTO inventario_upsert_demo (producto_id, stock)
VALUES (1, 8)
ON CONFLICT (producto_id) DO UPDATE
SET stock = excluded.stock;

SELECT producto_id, stock
FROM inventario_upsert_demo;
Resultado comprobado en SQLite 3.46.1
producto_idstock
18

La segunda inserción propone otra fila con producto_id = 1. Como esa clave ya existe, la rama DO UPDATE usa excluded.stock para tomar el valor que se intentaba insertar y actualizar la fila existente.

El objetivo del conflicto debe representar una regla real

En PostgreSQL y SQLite, el objetivo de ON CONFLICT suele identificar columnas sujetas a unicidad. No elijas una columna solo para “hacer funcionar” el UPSERT: debe existir una restricción o índice único que represente qué significa “la misma entidad” para tus datos.

Si tu regla es “un producto por producto_id”, la clave primaria es un buen árbitro. Si la regla real es “un usuario por email”, la decisión requiere pensar además cómo tratas NULL, cambios de email y normalización.

Cómo cambia entre motores

Variantes habituales del patrón UPSERT
MotorForma principalDetalle importante
PostgreSQLON CONFLICT (...) DO UPDATEPuede inferir o nombrar el conflicto de unicidad y expone la fila propuesta mediante excluded.
SQLiteON CONFLICT (...) DO UPDATESu UPSERT sigue el modelo de PostgreSQL con extensiones propias; tampoco es sintaxis SQL universal.
MySQL 8.4ON DUPLICATE KEY UPDATESe activa por una clave primaria o índice único duplicado; la documentación recomienda alias de fila frente al antiguo VALUES(col), que está obsoleto.
MySQL 8.4
INSERT INTO inventario (producto_id, stock)
VALUES (1, 8) AS nuevo
ON DUPLICATE KEY UPDATE
stock = nuevo.stock;

En MySQL, una tabla con varias claves únicas puede hacer menos evidente cuál dispara la actualización. La propia documentación recomienda cautela con ON DUPLICATE KEY UPDATE cuando existen múltiples índices únicos.

UPSERT no es lo mismo que MERGE

MERGE compara una fuente y una tabla destino y puede encadenar acciones diferentes según el resultado de esa coincidencia. Un UPSERT con INSERT ... ON CONFLICT parte de una fila que intentas insertar y define qué hacer si una regla de unicidad impide esa inserción. Se solapan en ciertos casos, pero no son intercambiables en todos los escenarios.

Errores frecuentes

Usar UPSERT sin una clave lógica clara

Si no sabes qué columnas identifican de forma única la entidad, la actualización puede afectar una fila distinta de la que esperabas. Define primero la regla de unicidad.

Sobrescribir columnas que no deberían cambiar

No copies todos los valores propuestos al DO UPDATE por comodidad. Actualiza solo los atributos que deban refrescarse cuando exista conflicto.

Copiar la sintaxis entre motores

ON CONFLICT y ON DUPLICATE KEY UPDATE no son la misma gramática. Mantén separada la intención común de la sintaxis específica de cada motor.

Práctica guiada

Intermedia · modificación segura

Inserta o actualiza el precio de un producto

En PostgreSQL o SQLite, la tabla precios(producto_id PRIMARY KEY, precio) ya puede contener el producto 7. Escribe un UPSERT que intente insertar (7, 39.90) y, si la clave ya existe, actualice solo precio.

Criterio de éxito: el conflicto se identifica con producto_id y la actualización toma el precio propuesto.

Qué debes recordar

  • UPSERT combina una inserción propuesta con una alternativa ante un conflicto de unicidad.
  • PostgreSQL y SQLite usan ON CONFLICT; MySQL usa ON DUPLICATE KEY UPDATE.
  • La regla única debe representar la identidad real de la fila.
  • Actualiza únicamente las columnas que deban cambiar.
  • UPSERT y MERGE pueden resolver problemas relacionados, pero no son equivalentes en general.

Conceptos relacionados

Fuentes técnicas consultadas