web-dev-qa-db-ja.com

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

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

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

  • Id(主キー)
  • Owner_Id(外部キー参照Id
  • Parent_Id(外部キー参照Id
  • nleft
  • nright
  • 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_Idの下にあるすべての行を削除しようとすると、問題が発生します。例:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

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

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

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

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

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

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

12
Alon Eitan
  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アクションを使用します。しかし、そのような行がない場合、削除は小さな問題になります。

    したがって、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 ; 
    
9
ypercubeᵀᴹ
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

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

5
a_vlad