Error de PostgreSQL: no unique or exclusion constraint matching the ON CONFLICT specification
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
INSERT ... ON CONFLICT necesita un árbitro: una restricción unique (o de exclusión) que defina qué significa "conflicto". El conflict target que escribiste debe coincidir con una exactamente — mismo conjunto de columnas, misma expresión, mismo predicado parcial. Sin coincidencia, no hay upsert.
Qué significa este error
PostgreSQL no adivina a qué restricción te refieres. ON CONFLICT (email) requiere un índice único sobre exactamente (email):
- Un índice no único sobre email no cuenta.
- Un índice único sobre (tenant_id, email) no coincide con un target de
(email)— ni al revés. - Un índice de expresión sobre
lower(email)coincide solo con el target(lower(email)). - Un índice único parcial (
WHERE deleted_at IS NULL) coincide solo con un target que tenga la misma cláusula WHERE.
Causas comunes
- La restricción unique simplemente no existe — la unicidad se asumió, nunca se declaró.
- El conjunto de columnas del target difiere del índice (olvidar la columna de tenant es el clásico en schemas multi-tenant).
- El índice es de expresión o parcial, y el conflict target no lo repite.
Cómo diagnosticarlo
\d users -- lista índices y restricciones con su definición exacta
Compara el conflict target del INSERT que falla con las definiciones de índice, comprobando columnas, expresiones y cláusulas WHERE literalmente.
Cómo solucionarlo
-- Falta la restricción → créala (en producción: CONCURRENTLY)
CREATE UNIQUE INDEX CONCURRENTLY users_email_key ON users (email);
-- Índice de expresión → el target debe repetirla:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (lower(email)) DO NOTHING;
-- Índice parcial → el target debe repetir el predicado:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (email) WHERE deleted_at IS NULL DO NOTHING;
Alternativamente, nombra la restricción directamente — ON CONFLICT ON CONSTRAINT users_email_key — aunque la forma de columna sobrevive mejor a los renombrados de restricción.
🔍
El error que ON CONFLICT existe para prevenir: duplicate key value violates unique constraint.
🔍
Lectura relacionada: Elegir el índice adecuado — índices únicos, parciales y de expresión.
