Errore 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 ha bisogno di un arbitro: un vincolo unique (o di esclusione) che definisce cosa significa "conflitto". Il conflict target che scrivi deve corrisponderne uno esattamente — stesso insieme di colonne, stessa espressione, stesso predicato parziale. Nessuna corrispondenza, nessun upsert.
Cosa significa questo errore
PostgreSQL non indovina quale vincolo intendi. ON CONFLICT (email) richiede un indice unique esattamente su (email):
- Un indice non-unique su email non conta.
- Un indice unique su (tenant_id, email) non corrisponde a un target di
(email)— né viceversa. - Un indice su espressione su
lower(email)corrisponde solo al target(lower(email)). - Un indice unique parziale (
WHERE deleted_at IS NULL) corrisponde solo a un target con la stessa clausola WHERE.
Cause comuni
- Il vincolo unique semplicemente non esiste — l'unicità era assunta, mai dichiarata.
- L'insieme di colonne del target differisce dall'indice (dimenticare la colonna del tenant è il classico negli schemi multi-tenant).
- L'indice è su un'espressione o parziale, e il conflict target non la ripete.
Come diagnosticarlo
\d users -- elenca indici e vincoli con la loro definizione esatta
Confronta il conflict target nell'INSERT fallito con le definizioni degli indici, controllando colonne, espressioni e clausole WHERE alla lettera.
Come risolverlo
-- Manca il vincolo → crealo (in produzione: CONCURRENTLY)
CREATE UNIQUE INDEX CONCURRENTLY users_email_key ON users (email);
-- Indice su espressione → il target deve ripeterla:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (lower(email)) DO NOTHING;
-- Indice parziale → il target deve ripetere il predicato:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (email) WHERE deleted_at IS NULL DO NOTHING;
In alternativa, nomina il vincolo direttamente — ON CONFLICT ON CONSTRAINT users_email_key — anche se la forma con le colonne sopravvive meglio ai rename dei vincoli.
🔍
L'errore che ON CONFLICT esiste per prevenire: duplicate key value violates unique constraint.
🔍
Lettura correlata: Scegliere l'indice giusto — indici unique, parziali e su espressione.
