MySQL - Elimina la riga che ha un vincolo di chiave esterna che fa riferimento a se stessa


12

Ho una tabella in cui memorizzo tutti i messaggi del forum pubblicati dagli utenti sul mio sito Web. La gerarchia dei messaggi strucrue viene implementata utilizzando un modello di set nidificato .

Di seguito è riportata una struttura semplificata della tabella:

  • Id (CHIAVE PRIMARIA)
  • Owner_Id (RIFERIMENTI CHIAVE ESTERI A ID )
  • Parent_Id (RIFERIMENTI CHIAVE ESTERI A ID )
  • Nleft
  • Nright
  • nLevel

Ora, la tabella è simile a questa:

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

Si noti che la prima riga è il messaggio principale e l'albero di questo post può essere visualizzato come:

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

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

Il mio problema si verifica quando provo a eliminare tutte le righe sotto lo stesso Owner_Idin una singola query. Esempio:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

La query precedente non riesce con il seguente errore:

Codice errore: 1451. Impossibile eliminare o aggiornare una riga principale: un vincolo di chiave esterna non riesce ( forumTbl, RIFERIMENTI Owner_Id_frgnCHIAVE ESTERA VINCOLI ( Owner_Id) forumTbl( Id) ON ELIMINA NO AZIONE ON AGGIORNAMENTO NO AZIONE)

Il motivo è che la prima riga , che è il nodo principale ( Id=1), ha anche lo stesso valore nel suo Owner_Idcampo ( Owner_Id=1) e causa l'interruzione della query a causa del vincolo della chiave esterna.

La mia domanda è: come posso evitare la circolarità di questo vincolo di chiave esterna ed eliminare una riga che fa riferimento a se stessa? C'è un modo per farlo senza prima dover aggiornare il file Owner_Iddella riga principale NULL?

Ho creato una demo di questo scenario: http://sqlfiddle.com/#!9/fd1b1

Grazie.

Risposte:


9
  1. Oltre a disabilitare le chiavi esterne che è pericoloso e può portare a incoerenze, ci sono altre due opzioni da considerare:

  2. Modifica i FOREIGN KEYvincoli con l' ON DELETE CASCADEopzione. Non ho testato tutti i casi, ma sicuramente ne hai bisogno per la (owner_id)chiave esterna e forse anche per l'altro.

    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 ;

    In questo caso, eliminare un nodo e tutti i discendenti dall'albero è più semplice. Si elimina un nodo e tutti i discendenti vengono eliminati tramite le azioni a cascata:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
  3. Il problema che hai affrontato è in realtà 2 problemi. Il primo è che l'eliminazione da una tabella con chiave esterna autoreferenziale non è un problema serio per MySQL, purché non vi sia alcuna riga che faccia riferimento a se stessa. Se c'è una riga, come nel tuo esempio, le opzioni sono limitate. Disabilita le chiavi esterne o usa l' CASCADEazione. Ma se non ci sono righe del genere, l'eliminazione diventa un problema minore.

    Pertanto, se decidiamo di archiviare NULLanziché lo stesso idin owner_id, puoi eliminare senza disabilitare le chiavi esterne e senza cascate.

    Ti imbatteresti quindi nel secondo problema! L'esecuzione della query genererebbe un errore simile:

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

    Errore / i, avviso / i:
    Impossibile eliminare o aggiornare una riga principale: un vincolo di chiave esterna non riesce (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) Forum di riferimento (id))

    Il motivo di questo errore sarebbe diverso rispetto a prima però. È perché MySQL controlla ogni vincolo dopo che ogni riga è stata eliminata e non (come dovrebbe) alla fine dell'istruzione. Pertanto, quando un genitore viene eliminato prima che il suo figlio venga eliminato, viene visualizzato un errore di vincolo di chiave esterna.

    Fortunatamente, esiste una soluzione semplice per questo, grazie al modello di set nidificato e al fatto che MySQL ci consente di impostare un ordine per le eliminazioni. Dobbiamo solo ordinare per nleft DESCo per nright DESC, il che assicura che tutti i bambini vengano eliminati prima di un genitore:

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

    Nota minore, potremmo (o dovremmo) usare una condizione che considera anche il modello nidificato. Questo è equivalente (e potrebbe utilizzare un indice su (nleft, nright)per trovare quali nodi eliminare:

    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;

semplicemente non dimenticare in questo caso È necessario analizzare manualmente le situazioni quando parent_id mostra 1, perché non si usa la cascata

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.