MySQL-それ自体を参照する外部キー制約を持つ行を削除します


12

ユーザーが私のウェブサイトに投稿したすべてのフォーラムメッセージを保存するテーブルがあります。メッセージ階層構造は、入れ子集合モデルを使用して実装されます

以下は、テーブルの単純化された構造です。

  • Id(主キー)
  • Owner_IdIDへの外部キー参照)
  • Parent_IdIDへの外部キー参照)
  • nleft
  • そろそろ
  • nlevel

これで、テーブルは次のようになります。

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 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_Id1つのクエリで同じ行のすべての行を削除しようとすると、問題が発生します。例:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

上記のクエリは次のエラーで失敗します。

エラーコード:1451。親行を削除または更新できません:外部キー制約が失敗しました(forumTbl、制約Owner_Id_frgn外部キー(Owner_Id)参照forumTblId)削除削除アクションなし更新アクションなし

その理由は、ルートノード()である最初の行のフィールド()にも同じ値があり、外部キー制約のためにクエリが失敗するためです。Id=1Owner_IdOwner_Id=1

私の質問は次のとおりです。この外部キー制約の循環を回避し、それ自体を参照する行を削除するにはどうすればよいですか ルート行のを最初に更新する必要なしにそれを行う方法はありますか?Owner_IdNULL

このシナリオのデモを作成しました:http : //sqlfiddle.com/#!9 / fd1b1

ありがとうございました。

回答:


9
  1. 危険で不整合を引き起こす可能性のある外部キーを無効にする以外に、考慮すべき他の2つのオプションがあります。

  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
  3. あなたが踏み込んだ問題は、実際には2つの問題です。1つ目は、自己参照する外部キーを持つテーブルからの削除は、それ自体を参照する行がない限り、MySQLにとって深刻な問題ではないということです。例のように行がある場合、オプションは制限されます。外部キーを無効にするか、CASCADEアクションを使用してください。しかし、そのような行がない場合、削除は小さな問題になります。

    そこで、我々は店に決定した場合NULLの代わりに同じidowner_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を使用して削除の順序を設定することです。by nleft DESCまたはby を注文するだけで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 ; 

5
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

この場合は忘れないでください。カスケードを使用しないため、parent_idが1になっている場合は手動で状況を解析する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.