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

12

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 (CLÉ PRIMAIRE)
  • Owner_Id (RÉFÉRENCES CLÉS ÉTRANGÈRES À Id )
  • Parent_Id (RÉFÉRENCES DE 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:

-- 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_Iddans 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_frgnFOREIGN KEY ( Owner_Id) REFERENCES forumTbl( Id) ON DELETE NO ACTION ON UPDATE NO 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_Idchamp ( Owner_Id=1), ce qui entraîne 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 au préalable Owner_Idla ligne racine de NULL?

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

Je vous remercie.

Alon Eitan
la source

Réponses:

9
  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. Modifiez les FOREIGN KEYcontraintes avec l' ON DELETE CASCADEoption. Je n'ai pas testé tous les cas, mais vous en avez sûrement besoin pour la (owner_id)clé étrangère et peut-être aussi pour l'autre.

    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' CASCADEaction. 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 NULLau lieu du même iddans owner_id, vous pouvez 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 DESCou 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 ; 
ypercubeᵀᴹ
la source
5
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

a_vlad
la source