Erreur 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".
Une clé étrangère a échoué dans l'une de ses deux directions : une ligne enfant pointe vers un parent qui n'est pas là, ou une ligne parente est en cours de suppression alors que des enfants la référencent encore. La formulation vous dit laquelle — « is not present in » versus « is still referenced from » — et le DETAIL nomme la valeur de clé exacte.
Ce que signifie cette erreur
Une clé étrangère est une promesse vérifiée aux deux bouts :
- « insert or update on child » : vous avez écrit une ligne enfant dont la valeur référençante n'a pas de ligne parente correspondante. L'écriture a été rejetée.
- « update or delete on parent » : supprimer (ou re-clé) le parent orphelinerait des enfants existants. Par défaut (
NO ACTION) PostgreSQL refuse ; la contrainte peut au contraire déclarerON DELETE CASCADE,SET NULL, etc. — une décision de schéma prise à la création de la contrainte, pas au moment de la requête.
Causes fréquentes
- ID erroné ou obsolète depuis l'application — le parent a été supprimé, ou l'ID vient d'un autre environnement.
- Ordonnancement entre transactions : l'INSERT du parent s'est exécuté dans une transaction différente qui n'a pas encore validé ; l'insertion de l'enfant ne peut pas le voir.
- Chargements en masse dans le mauvais ordre (enfants avant parents), ou chargements partiels.
- Suppression d'un parent sans stratégie pour ses enfants — le second message.
Comment la diagnostiquer
Le DETAIL vous donne le nom de la contrainte, la valeur de clé, et les deux noms de table. À partir de là :
-- La contrainte, en détail :
\d orders
-- Orphelins déjà présents (pour évaluer l'ampleur du problème dans les chargements) :
SELECT o.*
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE c.id IS NULL;
Comment la corriger
- Direction enfant : corrigez la valeur ou l'ordonnancement — créez d'abord le parent, dans la même transaction ou une transaction antérieure déjà validée.
- Chargements en masse : chargez les parents avant les enfants ; si les données arrivent réellement entrelacées, rendez la contrainte deferrable et vérifiez-la au commit :
ALTER TABLE orders ALTER CONSTRAINT orders_customer_id_fkey DEFERRABLE INITIALLY DEFERRED; - Direction parent : supprimez d'abord les enfants, ou déclarez l'intention dans le schéma (
ON DELETE CASCADEouSET NULL). Traitez CASCADE avec respect : un seul DELETE peut se propager silencieusement à énormément de lignes — faites-en un choix de conception conscient, pas une solution rapide. - Beaucoup de systèmes contournent entièrement la direction parent avec des suppressions logiques (une colonne
deleted_at) pour les entités dont dépendent d'autres données.
🔍
À lire aussi : Choisir le bon index — PostgreSQL n'indexe pas automatiquement le côté référençant d'une clé étrangère ; les suppressions sur le parent parcourent l'enfant sans lui.
