ON CONFLICT DO NOTHING convierte un INSERT en una operacion segura y repetible: si una fila viola una restriccion unica, PostgreSQL no falla, la omite en silencio. Es el caballo de batalla de las migraciones idempotentes, los sembradores y las tareas en segundo plano que pueden ejecutarse dos veces.
La insercion idempotente
Un INSERT normal con una clave duplicada lanza duplicate key value violates unique constraint y revierte el comando entero. Anade ON CONFLICT DO NOTHING y la fila en conflicto simplemente sale del conjunto a insertar, mientras las demas pasan.
INSERT INTO users (id, email, name, country)
VALUES (1, 'ada@example.com', 'Ada', 'GB')
ON CONFLICT (id) DO NOTHING;
Ejecuta esta consulta diez veces seguidas y el estado de la tabla tras la primera nunca cambia. Eso es idempotencia: el resultado depende de los datos, no de cuantas veces lanzaste la sentencia.
- El comando no falla: la transaccion sigue viva, sin
ROLLBACK.
- Solo se omite la fila en conflicto, no el lote entero.
- La fila existente queda intacta -- sus valores se mantienen como estaban.
Conflict target frente al nombre de la restriccion
PostgreSQL necesita saber que conflicto estas dispuesto a ignorar. Hay dos formas de indicarlo.
La primera es un conflict target: una lista de columnas (o una expresion) respaldada por un indice unico.
INSERT INTO users (email, name, country)
VALUES ('ada@example.com', 'Ada', 'GB')
ON CONFLICT (email) DO NOTHING;
La segunda es un nombre de restriccion explicito mediante ON CONSTRAINT:
INSERT INTO users (email, name, country)
VALUES ('ada@example.com', 'Ada', 'GB')
ON CONFLICT ON CONSTRAINT users_email_key DO NOTHING;
Y hay una tercera forma, la mas amplia -- ON CONFLICT DO NOTHING sin ningun objetivo: entonces PostgreSQL silencia un conflicto en cualquier restriccion unica o clave primaria.
Trampa: el DO NOTHING sin objetivo es peligroso. Si la tabla tiene a la vez PRIMARY KEY (id) y UNIQUE (email), la forma sin objetivo se traga un conflicto en cualquiera de las dos. Una fila con un id nuevo pero un email ya ocupado desaparece en silencio, y tu asumes que la insercion fue bien. Nombra un objetivo concreto cuando importe el motivo de la omision.
RETURNING calla las filas omitidas
RETURNING solo devuelve las filas que se insertaron de verdad. Las filas omitidas por un conflicto nunca aparecen en la salida -- una fuente frecuente de confusion.
INSERT INTO orders (id, user_id, amount, status)
VALUES (5001, 1, 99.90, 'paid')
ON CONFLICT (id) DO NOTHING
RETURNING id, status;
Si id = 5001 ya existe, la consulta devuelve cero filas. A nivel de aplicacion esto es comodo: un RETURNING vacio significa "la fila ya estaba". Si quieres la fila se haya insertado o no, necesitas un SELECT aparte o un DO UPDATE (ver el articulo sobre upsert).
- Insertada -- la fila esta en
RETURNING.
- Omitida por conflicto -- no hay fila.
- El
GET DIAGNOSTICS / rowcount del driver tambien informa 0 ante una omision.
Sembrado masivo de datos
El escenario mas comun es cargar un conjunto de referencia sin saber cuales ya existen. DO NOTHING elimina la necesidad de WHERE NOT EXISTS y de las condiciones de carrera que conlleva.
INSERT INTO employees (id, name, manager_id, dept, salary)
VALUES
(1, 'Grace', NULL, 'eng', 180000),
(2, 'Linus', 1, 'eng', 150000),
(3, 'Margaret', 1, 'eng', 150000)
ON CONFLICT (id) DO NOTHING;
Detalles utiles para inserciones por lotes:
- Dentro de un solo comando, los duplicados del conjunto de entrada tambien se filtran: gana la primera fila, las posteriores con la misma clave se omiten.
- Es seguro bajo concurrencia: una insercion competidora de la misma clave no tumbara tu
INSERT.
- Combina bien con
INSERT ... SELECT para mover datos desde un staging a la tabla principal sin duplicados.
Otros motores lo escriben distinto. En MySQL el analogo mas cercano es INSERT IGNORE INTO ..., pero tambien silencia algunos errores no relacionados (truncamiento, valores invalidos), asi que es menos preciso. En ClickHouse la unicidad no suele imponerse al insertar: la deduplicacion la hace el motor ReplacingMergeTree durante las fusiones, de forma asincrona, no en el momento del INSERT.
Cuando necesitas actualizar la fila existente en vez de omitirla, eso es ON CONFLICT ... DO UPDATE (upsert): una herramienta aparte con su propia semantica y la pseudotabla EXCLUDED, tratada en su propio articulo.
ON CONFLICT DO NOTHINGconvierte unINSERTen una operacion segura y repetible: si una fila viola una restriccion unica, PostgreSQL no falla, la omite en silencio. Es el caballo de batalla de las migraciones idempotentes, los sembradores y las tareas en segundo plano que pueden ejecutarse dos veces.La insercion idempotente
Un
INSERTnormal con una clave duplicada lanzaduplicate key value violates unique constrainty revierte el comando entero. AnadeON CONFLICT DO NOTHINGy la fila en conflicto simplemente sale del conjunto a insertar, mientras las demas pasan.INSERT INTO users (id, email, name, country) VALUES (1, 'ada@example.com', 'Ada', 'GB') ON CONFLICT (id) DO NOTHING;Ejecuta esta consulta diez veces seguidas y el estado de la tabla tras la primera nunca cambia. Eso es idempotencia: el resultado depende de los datos, no de cuantas veces lanzaste la sentencia.
ROLLBACK.Conflict target frente al nombre de la restriccion
PostgreSQL necesita saber que conflicto estas dispuesto a ignorar. Hay dos formas de indicarlo.
La primera es un conflict target: una lista de columnas (o una expresion) respaldada por un indice unico.
INSERT INTO users (email, name, country) VALUES ('ada@example.com', 'Ada', 'GB') ON CONFLICT (email) DO NOTHING;La segunda es un nombre de restriccion explicito mediante
ON CONSTRAINT:INSERT INTO users (email, name, country) VALUES ('ada@example.com', 'Ada', 'GB') ON CONFLICT ON CONSTRAINT users_email_key DO NOTHING;Y hay una tercera forma, la mas amplia --
ON CONFLICT DO NOTHINGsin ningun objetivo: entonces PostgreSQL silencia un conflicto en cualquier restriccion unica o clave primaria.RETURNING calla las filas omitidas
RETURNINGsolo devuelve las filas que se insertaron de verdad. Las filas omitidas por un conflicto nunca aparecen en la salida -- una fuente frecuente de confusion.INSERT INTO orders (id, user_id, amount, status) VALUES (5001, 1, 99.90, 'paid') ON CONFLICT (id) DO NOTHING RETURNING id, status;Si
id = 5001ya existe, la consulta devuelve cero filas. A nivel de aplicacion esto es comodo: unRETURNINGvacio significa "la fila ya estaba". Si quieres la fila se haya insertado o no, necesitas unSELECTaparte o unDO UPDATE(ver el articulo sobre upsert).RETURNING.GET DIAGNOSTICS/rowcountdel driver tambien informa 0 ante una omision.Sembrado masivo de datos
El escenario mas comun es cargar un conjunto de referencia sin saber cuales ya existen.
DO NOTHINGelimina la necesidad deWHERE NOT EXISTSy de las condiciones de carrera que conlleva.INSERT INTO employees (id, name, manager_id, dept, salary) VALUES (1, 'Grace', NULL, 'eng', 180000), (2, 'Linus', 1, 'eng', 150000), (3, 'Margaret', 1, 'eng', 150000) ON CONFLICT (id) DO NOTHING;Detalles utiles para inserciones por lotes:
INSERT.INSERT ... SELECTpara mover datos desde un staging a la tabla principal sin duplicados.Otros motores lo escriben distinto. En MySQL el analogo mas cercano es
INSERT IGNORE INTO ..., pero tambien silencia algunos errores no relacionados (truncamiento, valores invalidos), asi que es menos preciso. En ClickHouse la unicidad no suele imponerse al insertar: la deduplicacion la hace el motorReplacingMergeTreedurante las fusiones, de forma asincrona, no en el momento delINSERT.Cuando necesitas actualizar la fila existente en vez de omitirla, eso es
ON CONFLICT ... DO UPDATE(upsert): una herramienta aparte con su propia semantica y la pseudotablaEXCLUDED, tratada en su propio articulo.