MySQL - Löschen Sie eine Zeile mit einer Fremdschlüsseleinschränkung, die auf sich selbst verweist


12

Ich habe eine Tabelle, in der ich alle Forumsnachrichten speichere, die von den Benutzern auf meiner Website gepostet wurden. Die Nachrichten Hierarchie strucrue ist implementieren unter Verwendung eines Nested Sets .

Das Folgende ist eine vereinfachte Struktur der Tabelle:

  • Id (PRIMARY KEY)
  • OWNER_ID (FOREIGN KEY VERWEISE AUF Id )
  • PARENT_ID (FOREIGN KEY VERWEISE AUF Id )
  • nleft
  • in Ordnung
  • nlevel

Jetzt sieht der Tisch ungefähr so ​​aus:

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

Beachten Sie, dass die erste Zeile die Stammnachricht ist und der Baum dieses Beitrags wie folgt angezeigt werden kann:

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

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

Mein Problem tritt auf, wenn ich versuche, alle Zeilen unter derselben Owner_Idin einer einzigen Abfrage zu löschen . Beispiel:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

Die obige Abfrage schlägt mit folgendem Fehler fehl:

Fehlercode: 1451. Eine übergeordnete Zeile kann nicht gelöscht oder aktualisiert werden: Eine Fremdschlüsseleinschränkung schlägt fehl ( forumTbl, CONSTRAINT Owner_Id_frgnFOREIGN KEY ( Owner_Id) REFERENCES forumTbl( Id) ON DELETE NO ACTION ON UPDATE NO ACTION)

Der Grund dafür ist, dass die erste Zeile , bei der es sich um den Stammknoten ( Id=1) handelt, ebenfalls denselben Wert in ihrem Owner_IdFeld ( Owner_Id=1) hat und die Abfrage aufgrund der Fremdschlüsseleinschränkung fehlschlägt.

Meine Frage lautet: Wie kann ich diese Zirkularität von Fremdschlüsseleinschränkungen verhindern und eine Zeile löschen, die auf sich selbst verweist? Gibt es eine Möglichkeit, dies zu tun, ohne zuerst das der Stammzeile aktualisieren Owner_Idzu müssen NULL?

Ich habe eine Demo dieses Szenarios erstellt: http://sqlfiddle.com/#!9/fd1b1

Vielen Dank.

Antworten:


9
  1. Neben dem Deaktivieren von Fremdschlüsseln, die gefährlich sind und zu Inkonsistenzen führen können, sind zwei weitere Optionen zu berücksichtigen:

  2. Ändern Sie die FOREIGN KEYEinschränkungen mit der ON DELETE CASCADEOption. Ich habe nicht alle Fälle getestet, aber Sie benötigen dies sicherlich für den (owner_id)Fremdschlüssel und möglicherweise auch für den anderen.

    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 ;

    Wenn Sie dies tun, ist das Löschen eines Knotens und aller Nachkommen aus dem Baum einfacher. Sie löschen einen Knoten und alle Nachkommen werden durch die Kaskadenaktionen gelöscht:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
  3. Das Problem, in das Sie eingetreten sind, sind tatsächlich zwei Probleme. Das erste ist, dass das Löschen aus einer Tabelle mit selbstreferenzierendem Fremdschlüssel für MySQL kein ernstes Problem darstellt, solange es keine Zeile gibt, die auf sich selbst verweist. Wenn wie in Ihrem Beispiel eine Zeile vorhanden ist, sind die Optionen begrenzt. Deaktivieren Sie entweder Fremdschlüssel oder verwenden Sie die CASCADEAktion. Wenn es jedoch keine solchen Zeilen gibt, wird das Löschen zu einem kleineren Problem.

    Wenn wir uns also dafür entscheiden, NULLanstelle desselben idin zu speichern owner_id, können Sie löschen, ohne Fremdschlüssel zu deaktivieren und ohne Kaskaden.

    Sie würden dann auf das zweite Problem stoßen! Das Ausführen Ihrer Abfrage würde einen ähnlichen Fehler auslösen:

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

    Fehler, Warnung (en):
    Eine übergeordnete Zeile kann nicht gelöscht oder aktualisiert werden: Eine Fremdschlüsseleinschränkung schlägt fehl (rextester.forum, CONSTRAINT owner_id_frgn AUSLÄNDISCHER SCHLÜSSEL (owner_Id) REFERENZEN forum (id))

    Der Grund für diesen Fehler wäre jedoch anders als zuvor. Dies liegt daran, dass MySQL jede Einschränkung nach dem Löschen jeder Zeile überprüft und nicht (wie es sollte) am Ende der Anweisung. Wenn also ein Elternteil gelöscht wird, bevor sein Kind gelöscht wird, wird ein Fremdschlüsseleinschränkungsfehler angezeigt.

    Glücklicherweise gibt es dafür eine einfache Lösung, danke an das verschachtelte Mengenmodell, und mit MySQL können wir eine Reihenfolge für die Löschungen festlegen. Wir müssen nur nach nleft DESCoder nach bestellen, um nright DESCsicherzustellen, dass alle Kinder vor einem Elternteil gelöscht werden:

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

    Kleinere Anmerkung, wir könnten (oder sollten) eine Bedingung verwenden, die auch das verschachtelte Modell berücksichtigt. Dies ist äquivalent (und verwendet möglicherweise einen Index, (nleft, nright)um herauszufinden, welche Knoten gelöscht werden sollen:

    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;

Vergessen Sie in diesem Fall nur nicht, dass Sie Situationen, in denen parent_id auf 1 angezeigt wird, manuell analysieren müssen, da Sie keine Kaskade verwenden

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.