web-dev-qa-db-fra.com

MySQL - Supprimer une ligne qui a une contrainte de clé étrangère qui se réfère à elle-même

J'ai un tableau dans lequel je stocke tous les messages du forum publiés par les utilisateurs sur mon site Web. La structure de hiérarchie des messages est implémentée à l'aide d'un modèle d'ensemble imbriqué .

Voici une structure simplifiée du tableau:

  • Id (TOUCHE PRIMAIRE)
  • Owner_Id (RÉFÉRENCES DES CLÉS ÉTRANGÈRES À Id )
  • Parent_Id (RÉFÉRENCES DES CLÉS ÉTRANGÈRES À Id )
  • nleft
  • bien
  • nlevel

Maintenant, la table ressemble à ceci:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| Id      | Owner_Id      | Parent_Id      | nleft      | nright      | nlevel      |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 1       | 1             | NULL           | 1          | 8           | 1           |
| 2       | 1             | 1              | 2          | 5           | 2           |
| 3       | 1             | 2              | 3          | 4           | 3           |
| 4       | 1             | 1              | 6          | 7           | 2           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +

Notez que la première ligne est le message racine, et l'arborescence de ce message peut être affichée comme suit:

-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;

MESSAGE (Id = 1)
    MESSAGE (Id = 2)
        Message (Id = 3)
    Message (Id = 4)

Mon problème se produit lorsque j'essaie de supprimer toutes les lignes sous le même Owner_Id en une seule requête. Exemple:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

La requête ci-dessus échoue avec l'erreur suivante:

Code d'erreur: 1451. Impossible de supprimer ou de mettre à jour une ligne parent: une contrainte de clé étrangère échoue (forumTbl, CONSTRAINT Owner_Id_frgn CLÉ ÉTRANGÈRE (Owner_Id) RÉFÉRENCES forumTbl (Id) SUR SUPPRIMER PAS D'ACTION SUR MISE À JOUR PAS D'ACTION)

La raison en est que la première ligne , qui est le nœud racine (Id=1), a également la même valeur dans son Owner_Id champ (Owner_Id=1), et provoquant l'échec de la requête en raison de la contrainte de clé étrangère.

Ma question est: comment puis-je empêcher cette circularité de la contrainte de clé étrangère et supprimer une ligne qui fait référence à elle-même? Existe-t-il un moyen de le faire sans avoir à mettre à jour le Owner_Id de la ligne racine à NULL?

J'ai créé une démo de ce scénario: http://sqlfiddle.com/#!9/fd1b1

Je vous remercie.

12
Alon Eitan
  1. Outre la désactivation des clés étrangères, ce qui est dangereux et peut entraîner des incohérences, il existe deux autres options à considérer:

  2. Modifier le FOREIGN KEY contraintes avec le ON DELETE CASCADE option. Je n'ai pas testé tous les cas, mais vous en avez sûrement besoin pour le (owner_id) clé étrangère et éventuellement pour l'autre également.

    ALTER TABLE forum
        DROP FOREIGN KEY owner_id_frgn,
        DROP FOREIGN KEY parent_id_frgn ;
    ALTER TABLE forum
        ADD CONSTRAINT owner_id_frgn
            FOREIGN KEY (owner_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE,
        ADD CONSTRAINT parent_id_frgn
            FOREIGN KEY (parent_id) 
            REFERENCES forum (id)
            ON DELETE CASCADE ;
    

    Si vous faites cela, la suppression d'un nœud et de tous les descendants de l'arborescence est plus simple. Vous supprimez un nœud et tous les descendants sont supprimés via les actions en cascade:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
    
  3. Le problème que vous avez abordé est en fait 2 problèmes. La première est que la suppression d'une table avec une clé étrangère auto-référencée n'est pas un problème sérieux pour MySQL, tant qu'il n'y a pas de ligne qui se référence. S'il y a une ligne, comme dans votre exemple, les options sont limitées. Désactivez les clés étrangères ou utilisez l'action CASCADE. Mais s'il n'y a pas de telles lignes, la suppression devient un problème plus petit.

    Donc, si nous décidons de stocker NULL au lieu du même id dans owner_id, vous pouvez alors supprimer sans désactiver les clés étrangères et sans cascades.

    Vous tomberiez alors sur le deuxième problème! L'exécution de votre requête soulèverait une erreur similaire:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 ; 
    

    Erreur (s), avertissement (s):
    Impossible de supprimer ou de mettre à jour une ligne parent: une contrainte de clé étrangère échoue (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) REFERENCES forum (id))

    La raison de cette erreur serait cependant différente qu'auparavant. C'est parce que MySQL vérifie chaque contrainte après la suppression de chaque ligne et non (comme il se doit) à la fin de l'instruction. Ainsi, lorsqu'un parent est supprimé avant que son enfant ne soit supprimé, nous obtenons une erreur de contrainte de clé étrangère.

    Heureusement, il existe une solution simple pour cela, thnx au modèle d'ensemble imbriqué et à ce que MySQL nous permet de définir un ordre pour les suppressions. Il suffit de commander par nleft DESC ou par nright DESC, ce qui garantit que tous les enfants sont supprimés avant un parent:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 
    ORDER BY nleft DESC ; 
    

    Petite remarque, nous pourrions (ou devrions) utiliser une condition qui prend également en compte le modèle imbriqué. C'est équivalent (et peut utiliser un index sur (nleft, nright) pour trouver les nœuds à supprimer:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
    
9
ypercubeᵀᴹ
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

juste ne pas oublier dans ce cas, vous devez analyser manuellement les situations lorsque parent_id affiche 1, car vous n'utilisez pas la cascade

5
a_vlad