MySQL - Hapus baris yang memiliki batasan kunci asing yang merujuk pada dirinya sendiri

12

Saya memiliki tabel di mana saya menyimpan semua pesan forum yang diposting oleh pengguna di situs web saya. Strucrue pesan hierarki diimplementasikan menggunakan model himpunan bersarang .

Berikut ini adalah struktur tabel yang disederhanakan:

  • Id (KUNCI UTAMA)
  • Owner_Id (REFERENSI KUNCI ASING UNTUK ID )
  • Parent_Id (REFERENSI KUNCI ASING UNTUK ID )
  • tidak tidur
  • baiklah
  • nlevel

Sekarang, tabelnya terlihat seperti ini:

+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 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           |
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +

Perhatikan bahwa baris pertama adalah pesan root, dan pohon postingan ini dapat ditampilkan sebagai:

-- SELECT * FROM forumTbl WHERE Owner_Id = 1 ORDER BY nleft;

MESSAGE (Id = 1)
    MESSAGE (Id = 2)
        Message (Id = 3)
    Message (Id = 4)

Masalah saya terjadi ketika saya mencoba menghapus semua baris di bawah yang sama Owner_Iddalam satu permintaan. Contoh:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

Permintaan di atas gagal dengan kesalahan berikut:

Kode Kesalahan: 1451. Tidak dapat menghapus atau memperbarui baris induk: batasan kunci asing gagal ( forumTbl, CONSTRAINT Owner_Id_frgnFOREIGN KEY ( Owner_Id) REFERENSI forumTbl( Id) PADA HAPUS TANPA TINDAKAN PADA PEMBARUAN TANPA TINDAKAN)

Alasannya adalah bahwa baris pertama , yang merupakan simpul root ( Id=1), juga memiliki nilai yang sama di Owner_Idbidangnya ( Owner_Id=1), dan menyebabkan permintaan gagal karena batasan kunci asing.

Pertanyaan saya adalah: Bagaimana saya bisa mencegah bundaran kendala kunci asing ini dan menghapus baris yang merujuk pada dirinya sendiri? Apakah ada cara untuk melakukannya tanpa terlebih dahulu harus memperbarui the Owner_Iddari baris root NULL?

Saya membuat demo skenario ini: http://sqlfiddle.com/#!9/fd1b1

Terima kasih.

Alon Eitan
sumber

Jawaban:

9
  1. Selain menonaktifkan kunci asing yang berbahaya dan dapat menyebabkan inkonsistensi, ada dua opsi lain yang perlu dipertimbangkan:

  2. Ubah FOREIGN KEYkendala dengan ON DELETE CASCADEopsi. Saya belum menguji semua kasus tetapi Anda pasti membutuhkan ini untuk (owner_id)kunci asing dan mungkin untuk yang lain juga.

    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 ;

    Jika Anda melakukan ini, maka menghapus simpul dan semua keturunan dari pohon lebih sederhana. Anda menghapus simpul dan semua keturunan dihapus melalui tindakan kaskade:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
  3. Masalah yang Anda melangkahi sebenarnya adalah 2 masalah. Yang pertama adalah bahwa menghapus dari tabel dengan kunci asing referensi sendiri bukan masalah serius bagi MySQL, selama tidak ada baris yang mereferensikannya. Jika ada baris, seperti pada contoh Anda, opsi terbatas. Nonaktifkan kunci asing atau gunakan CASCADEtindakan. Tetapi jika tidak ada baris seperti itu, menghapus menjadi masalah yang lebih kecil.

    Jadi, jika kita memutuskan untuk menyimpan NULLbukannya sama iddi owner_id, maka Anda bisa menghapus tanpa menonaktifkan kunci asing dan tanpa cascades.

    Anda kemudian akan menemukan masalah kedua! Menjalankan kueri Anda akan memunculkan kesalahan serupa:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 ; 

    Kesalahan, peringatan:
    Tidak dapat menghapus atau memperbarui baris induk: batasan kunci asing gagal (rextester.forum, CONSTRAINT owner_id_frgn KUNCI ASING (pemilik_Id) REFERENSI forum (id))

    Alasan untuk kesalahan ini akan berbeda dari sebelumnya. Itu karena MySQL memeriksa setiap kendala setelah setiap baris dihapus dan tidak (sebagaimana mestinya) pada akhir pernyataan. Jadi, ketika orangtua dihapus sebelum anaknya dihapus, kami mendapatkan kesalahan batasan kunci asing.

    Untungnya, ada solusi sederhana untuk ini, dari model set bersarang dan untuk itu MySQL memungkinkan kita untuk mengatur pesanan untuk penghapusan. Kami hanya perlu memesan pada nleft DESCatau oleh nright DESC, yang memastikan bahwa semua anak dihapus sebelum orang tua:

    DELETE FROM forum 
    WHERE owner_id = 1 OR id = 1 
    ORDER BY nleft DESC ; 

    Catatan kecil, kita bisa (atau harus) menggunakan kondisi yang mempertimbangkan model bersarang juga. Ini setara (dan mungkin menggunakan indeks (nleft, nright)untuk menemukan node mana yang akan dihapus:

    DELETE FROM forum 
    WHERE nleft >= 1 AND nright <= 8 
    ORDER BY nleft DESC ; 
ypercubeᵀᴹ
sumber
5
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM forum WHERE Owner_Id = 1 ORDER BY nright;
SET FOREIGN_KEY_CHECKS=1;

jangan lupa dalam hal ini Anda harus secara manual mem-parsing situasi ketika parent_id menunjukkan ke 1, karena Anda tidak menggunakan kaskade

a_vlad
sumber