Upsert con INSERT ... ON CONFLICT
¿Cómo hacer un upsert en PostgreSQL con INSERT ... ON CONFLICT?
INSERT ... ON CONFLICT inserta una fila o, si eso violara una restricción de unicidad, actualiza la fila existente o la omite, todo en un solo paso atómico.
Upsert significa insertar o actualizar (insert or update). PostgreSQL lo implementa con INSERT ... ON CONFLICT: cuando la nueva fila violaría una restricción de unicidad o de exclusión, DO NOTHING la omite y DO UPDATE actualiza en su lugar la fila en conflicto.
ON CONFLICT DO UPDATE garantiza un resultado atómico de inserción o actualización incluso con sesiones concurrentes, así que no necesitas un SELECT aparte seguido de un INSERT o UPDATE. La tabla especial EXCLUDED hace referencia a la fila que se propuso insertar. El destino del conflicto, como (path), debe coincidir con un índice o una restricción de unicidad.
Ejemplo
El ejemplo ejecuta el mismo upsert dos veces. El primero inserta /home con 1 visita; el segundo choca con la clave primaria y suma 1, así que el conteo final es 2.
CREATE TABLE page_views (path text PRIMARY KEY, views bigint NOT NULL);
INSERT INTO page_views VALUES ('/home', 1)
ON CONFLICT (path) DO UPDATE SET views = page_views.views + EXCLUDED.views;
INSERT INTO page_views VALUES ('/home', 1)
ON CONFLICT (path) DO UPDATE SET views = page_views.views + EXCLUDED.views;
SELECT * FROM page_views;SQL simple para PostgreSQL 16 o 17 estándar, sin extensiones. Ejecútalo en una base de datos vacía que puedas descartar.
Fuentes
Documentación oficial de PostgreSQL (en inglés).