ON DELETE: o que acontece às linhas que ficam para trás
Ao criar uma chave estrangeira, há uma decisão que muita gente deixa passar em branco: o que fazer aos filhos quando o pai é apagado. O silêncio também é uma escolha — e nem sempre a certa.
1. As três respostas
-- Recusa apagar o pai enquanto houver filhos (a predefinição)
ON DELETE RESTRICT
-- Apaga os filhos com o pai
ON DELETE CASCADE
-- Deixa os filhos órfãos, com a referência a NULL
ON DELETE SET NULL2. Escolher pelo significado, não pela conveniência
A pergunta a fazer é: este filho quer dizer alguma coisa sem o pai?
Comentários de um artigo → CASCADE. Um comentário sem o artigo que comentava não quer dizer nada. Pior: com
SET NULL, um comentário a um artigo apagado apareceria de repente noutro sítio do site, sem nada por perto que o explicasse.Encomendas de um cliente → RESTRICT. A encomenda tem valor contabilístico próprio. Apagar o cliente não pode levar o histórico de vendas com ele — e é melhor a base recusar do que descobrir isso depois.
Artigos de uma categoria → SET NULL (ou RESTRICT). O artigo continua a existir sem a categoria; só fica por classificar.
3. O cascade é silencioso, e é isso que assusta
DELETE FROM artigos WHERE id = 42;
-- DELETE 1A base diz que apagou uma linha. Apagou uma linha e os 87 comentários que lá estavam pendurados, mais os votos, mais as ligações às etiquetas. Não há aviso, e não há como desfazer.
É por isso que vale a pena, na aplicação, perguntar antes:
const comentarios = await db.comentarios.count({ where: { artigoId: id } });
if (comentarios > 0) {
return { ok: false, mensagem: `Este artigo tem ${comentarios} comentários. Apagar leva-os também.` };
}4. E se a chave estrangeira não existir?
Aí não há decisão nenhuma a tomar: ficam linhas a apontar para ids que já não existem, ninguém dá por isso, e um dia uma consulta com JOIN devolve menos resultados do que devia. As chaves estrangeiras não são burocracia — são a única coisa que impede a base de se contradizer a si própria.