Errore PostgreSQL: violates foreign key constraint
ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey"
DETAIL: Key (customer_id)=(42) is not present in table "customers".
-- direzione opposta:
ERROR: update or delete on table "customers" violates foreign key constraint "orders_customer_id_fkey" on table "orders"
DETAIL: Key (id)=(42) is still referenced from table "orders".
Una chiave esterna è fallita in una delle sue due direzioni: una riga figlia punta a un padre che non c'è, oppure una riga padre viene rimossa mentre dei figli la referenziano ancora. La formulazione ti dice quale — "is not present in" contro "is still referenced from" — e il DETAIL indica il valore esatto della chiave.
Cosa significa questo errore
Una chiave esterna è una promessa verificata a entrambe le estremità:
- "insert or update on child": hai scritto una riga figlia il cui valore referenziante non ha una riga padre corrispondente. La scrittura è stata rifiutata.
- "update or delete on parent": rimuovere (o re-key) il padre renderebbe orfani i figli esistenti. Di default (
NO ACTION) PostgreSQL rifiuta; il vincolo può invece dichiarareON DELETE CASCADE,SET NULL, ecc. — una decisione di schema presa alla creazione del vincolo, non al momento della query.
Cause comuni
- ID sbagliato o obsoleto dall'applicazione — il padre è stato eliminato, o l'ID viene da un altro ambiente.
- Ordinamento tra transazioni: l'INSERT del padre è girato in una transazione diversa che non ha ancora fatto commit; l'insert del figlio non riesce a vederlo.
- Bulk load nell'ordine sbagliato (figli prima dei padri), o load parziali.
- Cancellare un padre senza una strategia per i suoi figli — il secondo messaggio.
Come diagnosticarlo
Il DETAIL ti dà il nome del vincolo, il valore della chiave e i nomi di entrambe le tabelle. Da lì:
-- Il vincolo, per esteso:
\d orders
-- Orfani già presenti (per capire l'ampiezza del problema nei load):
SELECT o.*
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE c.id IS NULL;
Come risolverlo
- Direzione figlio: correggi il valore o l'ordinamento — crea prima il padre, nella stessa transazione o in una precedente già committata.
- Bulk load: carica i padri prima dei figli; se i dati arrivano davvero mescolati, rendi il vincolo deferrable e verificalo al commit:
ALTER TABLE orders ALTER CONSTRAINT orders_customer_id_fkey DEFERRABLE INITIALLY DEFERRED; - Direzione padre: cancella prima i figli, o dichiara l'intento nello schema (
ON DELETE CASCADEoSET NULL). Tratta CASCADE con rispetto: un solo DELETE può propagarsi silenziosamente a molte righe — rendilo una scelta di design consapevole, non una soluzione veloce. - Molti sistemi aggirano del tutto la direzione padre con i soft delete (una colonna
deleted_at) per le entità a cui altri dati si appoggiano.
🔍
Lettura correlata: Scegliere l'indice giusto — PostgreSQL non indicizza automaticamente il lato referenziante di una chiave esterna; i delete sul padre fanno scan del figlio senza uno.
