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.
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
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;| producto_id | stock |
|---|---|
| 1 | 8 |
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
| Motor | Forma principal | Detalle importante |
|---|---|---|
| PostgreSQL | ON CONFLICT (...) DO UPDATE | Puede inferir o nombrar el conflicto de unicidad y expone la fila propuesta mediante excluded. |
| SQLite | ON CONFLICT (...) DO UPDATE | Su UPSERT sigue el modelo de PostgreSQL con extensiones propias; tampoco es sintaxis SQL universal. |
| MySQL 8.4 | ON DUPLICATE KEY UPDATE | Se activa por una clave primaria o índice único duplicado; la documentación recomienda alias de fila frente al antiguo VALUES(col), que está obsoleto. |
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
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.
INSERT INTO ... VALUES.DO UPDATE, excluded.precio representa el valor propuesto.Solución razonada
INSERT INTO precios (producto_id, precio)
VALUES (7, 39.90)
ON CONFLICT (producto_id) DO UPDATE
SET precio = excluded.precio;La inserción normal ocurre si el producto no existe. Si la clave primaria ya está ocupada, solo se sustituye precio; no se modifica ninguna otra columna por accidente.
Qué debes recordar
- UPSERT combina una inserción propuesta con una alternativa ante un conflicto de unicidad.
- PostgreSQL y SQLite usan
ON CONFLICT; MySQL usaON DUPLICATE KEY UPDATE. - La regla única debe representar la identidad real de la fila.
- Actualiza únicamente las columnas que deban cambiar.
- UPSERT y
MERGEpueden resolver problemas relacionados, pero no son equivalentes en general.