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:
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
, CONSTRAINTOwner_Id_frgn
CLÉ ÉTRANGÈRE (Owner_Id
) RÉFÉRENCESforumTbl
(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.
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:
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
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 ;
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