Tenho uma tabela na qual armazeno todas as mensagens do fórum postadas pelos usuários no meu site. A estrutura de hierarquia de mensagens é implementada usando um modelo de conjunto aninhado .
A seguir, uma estrutura simplificada da tabela:
- ID (CHAVE PRIMÁRIA)
- Owner_Id (REFERÊNCIAS DE CHAVE ESTRANGEIRA PARA Id )
- Parent_Id (REFERÊNCIAS DE CHAVE ESTRANGEIRA PARA Id )
- esquerda
- certo
- nível
Agora, a tabela está mais ou menos assim:
+ ------- + ------------- + -------------- + ---------- + ----------- + ----------- +
| 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 deste post 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 sob o mesmo Owner_Id
em 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
, CONSTRAINTOwner_Id_frgn
FOREIGN KEY (Owner_Id
) REFERENCESforumTbl
(Id
) ON DELETE NO ACTION ON UPDATE NO ACTION)
O motivo é que a primeira linha , que é o nó raiz ( Id=1
), também tem o mesmo valor em seu Owner_Id
campo ( Owner_Id=1
), e isso faz com que a consulta falhe devido à restrição de chave estrangeira.
Minha pergunta é: Como posso evitar essa circularidade de restrição de chave estrangeira e excluir uma linha que faz referência a si mesma? Existe uma maneira de fazer isso sem primeiro ter que atualizar o Owner_Id
da linha raiz para NULL
?
Eu criei uma demonstração deste cenário: http://sqlfiddle.com/#!9/fd1b1
Obrigada.
Além de desabilitar as chaves estrangeiras, o que é perigoso e pode levar a inconsistências, há duas outras opções a serem consideradas:
Modifique as
FOREIGN KEY
restrições com aON DELETE CASCADE
opção. Eu não testei todos os casos, mas você certamente precisa disso para a(owner_id)
chave estrangeira e possivelmente para o outro também.Se você fizer isso, a exclusão de um nó e todos os descendentes da árvore será mais simples. Você exclui um nó e todos os descendentes são excluídos por meio das ações em cascata:
O problema em que você entrou é, na verdade, 2 problemas. A primeira é que deletar de uma tabela com chave estrangeira auto-referenciada não é um problema sério para o MySQL, desde que não haja 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 as chaves estrangeiras ou use a
CASCADE
ação. Mas se não houver essas linhas, a exclusão se tornará um problema menor.Portanto, se decidirmos armazenar
NULL
em vez do mesmoid
emowner_id
, você poderá excluir sem desabilitar chaves estrangeiras e sem cascatas.Você então tropeçaria no segundo problema! Executar sua consulta geraria um erro semelhante:
A razão para este 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, por causa do modelo de conjunto aninhado e para isso o MySQL nos permite definir uma ordem para as exclusões. Nós apenas temos que ordenar por
nleft DESC
ou pornright DESC
, o que garante que todos os filhos sejam excluídos antes de um pai:Nota menor, podemos (ou devemos) usar uma condição que considere também o modelo aninhado. Isso é equivalente (e pode usar um índice
(nleft, nright)
para descobrir quais nós excluir:apenas não esqueça neste caso Você deve analisar manualmente as situações quando parent_id mostrar para 1, porque você não usa cascata
Espero que essas respostas ajudem outras pessoas.
Mostrar restrições de tabela
Se você não conhece as restrições de tabela, isso ajudará.
Para excluir dados de várias tabelas no banco de dados, é necessário eliminar as restrições primeiro e, em seguida, ADICIONAR CONSTRAINT.
O nome da restrição pode ser simplificado:
_foreign
no finalExemplo:
Ref1: https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html Ref2: https://www.mysqltutorial.org/mysql-foreign-key/