ユーザーが自分のWebサイトに投稿したすべてのフォーラムメッセージを保存するテーブルがあります。メッセージ階層構造は、 ネストされたセットモデル を使用して実装されます。
以下は、テーブルの単純化された構造です。
これで、テーブルは次のようになります。
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 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 |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
最初の行はルートメッセージであり、この投稿のツリーは次のように表示できることに注意してください。
-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;
MESSAGE (Id = 1)
MESSAGE (Id = 2)
Message (Id = 3)
Message (Id = 4)
単一のクエリで同じOwner_Id
の下にあるすべての行を削除しようとすると、問題が発生します。例:
DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;
上記のクエリは次のエラーで失敗します。
エラーコード:1451。親行を削除または更新できません:外部キー制約が失敗します(
forumTbl
、CONSTRAINTOwner_Id_frgn
FOREIGN KEY(Owner_Id
)REFERENCESforumTbl
(Id
)削除時にアクションなし、更新時にアクションなし)
その理由は、ルートノード(Id=1
)である最初の行もOwner_Id
フィールド(Owner_Id=1
)。外部キー制約のためにクエリが失敗します。
私の質問は、この外部キー制約の循環を防止し、それ自体を参照する行を削除するにはどうすればよいですか?それを行う方法はありますかなし最初にルート行のOwner_Id
をNULL
に更新する必要がありますか?
このシナリオのデモを作成しました: http://sqlfiddle.com/#!9/fd1b1
ありがとうございました。
危険で不整合を引き起こす可能性のある外部キーを無効にすることに加えて、考慮すべき他の2つのオプションがあります。
FOREIGN KEY
オプションを使用してON DELETE CASCADE
制約を変更します。私はすべてのケースをテストしたわけではありませんが、(owner_id)
外部キーとおそらく他にも同様にこれが必要です。
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 ;
これを行うと、ノードとすべての子孫をツリーから削除する方が簡単です。ノードを削除すると、カスケードアクションによってすべての子孫が削除されます。
DELETE FROM forum
WHERE id = 1 ; -- deletes id=1 and all descendants
あなたが踏み込んだ問題は、実際には2つの問題です。 1つ目は、自己参照する外部キーを持つテーブルからの削除は、それ自体を参照する行がない限り、MySQLにとって深刻な問題ではないということです。例のように行がある場合、オプションは制限されます。外部キーを無効にするか、CASCADE
アクションを使用します。しかし、そのような行がない場合、削除は小さな問題になります。
したがって、owner_id
に同じNULL
ではなくid
を格納することにした場合、外部キーを無効にせず、カスケードなしで削除できます。
次に、2番目の問題に遭遇します。クエリを実行すると、同様のエラーが発生します。
DELETE FROM forum
WHERE owner_id = 1 OR id = 1 ;
エラー、警告:
親行を削除または更新できません:外部キー制約が失敗しました(rextester.forum、CONSTRAINT owner_id_frgn FOREIGN KEY(owner_Id)REFERENCES forum(id))
このエラーの理由は以前とは異なります。これは、MySQLがすべての行が削除された後に各制約をチェックするためであり、ステートメントの終わりではなく(そうであるように)ではありません。したがって、子が削除される前に親が削除されると、外部キー制約エラーが発生します。
幸いなことに、これには簡単な解決策があります。それは、入れ子になったセットモデルと、MySQLを使用して削除の順序を設定することです。 nleft DESC
またはnright DESC
で並べ替えるだけで、すべての子が親の前に削除されます。
DELETE FROM forum
WHERE owner_id = 1 OR id = 1
ORDER BY nleft DESC ;
細かい注意として、ネストされたモデルを考慮する条件も使用できます(または使用する必要があります)。これは同等です(また、(nleft, nright)
のインデックスを使用して、削除するノードを見つけることができます。
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;
この場合は忘れないでください。カスケードを使用しないため、parent_idが1になっている場合は手動で状況を解析する必要があります。