MySQL - Excluir linha que possui uma restrição de chave estrangeira que se refere a si mesma

12

Eu tenho uma tabela na qual guardo todas as mensagens do fórum postadas pelos usuários no meu site. A estrutura da hierarquia de mensagens é implementada usando um modelo de conjunto aninhado .

A seguir, é apresentada uma estrutura simplificada da tabela:

  • ID (CHAVE PRIMÁRIA)
  • Owner_Id (REFERÊNCIAS DA CHAVE ESTRANGEIRA AO ID )
  • Parent_Id (REFERÊNCIAS DA CHAVE ESTRANGEIRA AO ID )
  • nleft
  • certo
  • nlevel

Agora, a tabela está parecida com esta:

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

Observe que a primeira linha é a mensagem raiz e a árvore desta postagem pode ser exibida como:

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

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

Meu problema ocorre quando tento excluir todas as linhas da mesma Owner_Idem uma única consulta. Exemplo:

DELETE FROM forumTbl WHERE Owner_Id = 1 ORDER BY nright;

A consulta acima falha com o seguinte erro:

Código de erro: 1451. Não é possível excluir ou atualizar uma linha pai: uma restrição de chave estrangeira falha ( forumTbl, CONSTRAINT Owner_Id_frgnFOREIGN KEY ( Owner_Id) REFERÊNCIAS forumTbl( Id) EM EXCLUIR SEM AÇÃO NA ATUALIZAÇÃO SEM AÇÃO)

O motivo é que a primeira linha , que é o nó raiz ( Id=1), também possui o mesmo valor em seu Owner_Idcampo ( Owner_Id=1) e causa falha na consulta devido à restrição de chave estrangeira.

Minha pergunta é: Como posso evitar essa circularidade de restrição de chave estrangeira e excluir uma linha que faça referência a si mesma? Existe uma maneira de fazer isso sem primeiro precisar atualizar o Owner_Idda linha raiz NULL?

Criei uma demonstração desse cenário: http://sqlfiddle.com/#!9/fd1b1

Obrigado.

Alon Eitan
fonte

Respostas:

9
  1. Além de desativar chaves estrangeiras, o que é perigoso e pode levar a inconsistências, há duas outras opções a serem consideradas:

  2. Modifique as FOREIGN KEYrestrições com a ON DELETE CASCADEopção Eu não testei todos os casos, mas você certamente precisa disso para a (owner_id)chave estrangeira e possivelmente para a outra também.

    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 ;

    Se você fizer isso, a exclusão de um nó e de todos os descendentes da árvore é mais simples. Você exclui um nó e todos os descendentes são excluídos através das ações em cascata:

    DELETE FROM forum
    WHERE id = 1 ;         -- deletes id=1 and all descendants
  3. O problema em que você entrou é, na verdade, dois problemas. A primeira é que a exclusão de uma tabela com chave estrangeira auto-referente não é um problema sério para o MySQL, desde que não exista nenhuma linha que faça referência a si mesma. Se houver uma linha, como no seu exemplo, as opções são limitadas. Desative chaves estrangeiras ou use a CASCADEação. Mas se não houver essas linhas, a exclusão se tornará um problema menor.

    Então, se nós decidir armazenar NULLem vez do mesmo idem owner_id, então você pode apagar sem desativar chaves estrangeiras e sem cascatas.

    Você tropeçaria no segundo problema! A execução da sua consulta geraria um erro semelhante:

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

    Erro (s), aviso (s):
    Não é possível excluir ou atualizar uma linha pai: uma restrição de chave estrangeira falha (rextester.forum, CONSTRAINT owner_id_frgn FOREIGN KEY (owner_Id) fórum de REFERÊNCIAS (id))

    O motivo desse erro seria diferente do que antes. É porque o MySQL verifica cada restrição depois que cada linha é excluída e não (como deveria) no final da instrução. Portanto, quando um pai é excluído antes que seu filho seja excluído, obtemos um erro de restrição de chave estrangeira.

    Felizmente, existe uma solução simples para isso, thnx ao modelo de conjunto aninhado e ao MySQL nos permite definir uma ordem para as exclusões. Nós apenas precisamos ordenar por nleft DESCou por nright DESC, o que garante que todos os filhos sejam excluídos antes dos pais:

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

    Nota menor, poderíamos (ou deveríamos) usar uma condição que também considera o modelo aninhado. Isso é equivalente (e pode usar um índice (nleft, nright)para encontrar quais nós excluir:

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

apenas não esqueça neste caso Você deve analisar manualmente situações quando parent_id mostrar 1, porque Você não usa cascata

a_vlad
fonte