DELETE: removendo dados com segurança

1. Sintaxe Básica do DELETE

A instrução DELETE é um dos comandos DML (Data Manipulation Language) mais poderosos e perigosos do SQL. Sua sintaxe fundamental é enganosamente simples:

DELETE FROM nome_da_tabela WHERE condicao;

A estrutura básica parece inofensiva, mas o verdadeiro poder — e risco — está na cláusula WHERE. Se você omitir o WHERE, o comando se torna uma arma de destruição em massa:

DELETE FROM clientes;  -- Remove TODOS os registros da tabela!

Diferença crucial entre DELETE e TRUNCATE:

Característica DELETE TRUNCATE
Remove linhas Sim Sim
Pode usar WHERE Sim Não
Log de transação Completo Mínimo
Velocidade Lento Rápido
Reseta auto-increment Não Sim
Pode ser revertido Sim (com transação) Depende do banco

Enquanto o DELETE opera linha por linha e mantém logs detalhados, o TRUNCATE remove páginas inteiras de dados de uma vez, sendo mais rápido mas menos flexível.

2. Filtrando Registros com WHERE

A cláusula WHERE é sua principal ferramenta para precisão. Vamos explorar operadores essenciais:

Operadores de comparação básicos:

DELETE FROM produtos WHERE preco = 0;
DELETE FROM usuarios WHERE idade < 18;
DELETE FROM pedidos WHERE status <> 'entregue';
DELETE FROM logs WHERE data_criacao > '2024-01-01';

Combinação de condições:

DELETE FROM transacoes 
WHERE valor > 10000 
  AND data_transacao < '2023-01-01'
  AND status = 'cancelada';

Uso de IN para múltiplos valores:

DELETE FROM categorias 
WHERE id IN (10, 15, 20, 25, 30);

Intervalos com BETWEEN:

DELETE FROM auditoria 
WHERE data_registro BETWEEN '2020-01-01' AND '2020-12-31';

Padrões com LIKE:

DELETE FROM usuarios_temporarios 
WHERE email LIKE '%@teste-descartavel.com';

3. Exclusão com JOINs e Subconsultas

Quando a lógica de exclusão envolve múltiplas tabelas, você precisa de técnicas mais avançadas.

Exclusão com JOIN no MySQL:

DELETE p
FROM pedidos p
INNER JOIN clientes c ON p.cliente_id = c.id
WHERE c.ativo = 0 AND p.data_pedido < '2023-01-01';

Exclusão com USING no PostgreSQL:

DELETE FROM pedidos p
USING clientes c
WHERE p.cliente_id = c.id 
  AND c.ativo = 0 
  AND p.data_pedido < '2023-01-01';

Subconsultas para filtrar registros relacionados:

DELETE FROM itens_pedido
WHERE pedido_id IN (
    SELECT id FROM pedidos 
    WHERE data_criacao < '2022-01-01' 
    AND status = 'cancelado'
);

Exclusão em cascata vs. manual:

Se você tem chaves estrangeiras com ON DELETE CASCADE, excluir um registro pai remove automaticamente os filhos. Caso contrário, você precisa excluir manualmente na ordem correta:

-- Primeiro os filhos
DELETE FROM itens_pedido WHERE pedido_id = 123;
-- Depois o pai
DELETE FROM pedidos WHERE id = 123;

4. Transações e Rollback para Segurança

Nunca subestime o poder de uma transação. Ela é sua rede de segurança:

BEGIN TRANSACTION;

-- Verificação prévia
SELECT COUNT(*) FROM funcionarios WHERE salario > 50000;

-- Exclusão planejada
DELETE FROM funcionarios WHERE salario > 50000;

-- Se algo errado, desfaz tudo
ROLLBACK;

-- Se tudo certo, confirma
COMMIT;

SAVEPOINT para rollback parcial:

BEGIN TRANSACTION;

SAVEPOINT antes_delete_importante;
DELETE FROM dados_criticos WHERE id = 100;

-- Se precisar desfazer apenas essa exclusão
ROLLBACK TO SAVEPOINT antes_delete_importante;

COMMIT;

Verificação prévia com SELECT:

Sempre execute um SELECT com a mesma condição antes do DELETE:

-- Passo 1: Verificar o que será afetado
SELECT COUNT(*), * FROM pedidos 
WHERE data_criacao < '2020-01-01' 
  AND status = 'finalizado';

-- Passo 2: Executar o DELETE (idealmente dentro de uma transação)
DELETE FROM pedidos 
WHERE data_criacao < '2020-01-01' 
  AND status = 'finalizado';

5. Boas Práticas de Segurança

Sempre testar com SELECT primeiro:

Nunca execute um DELETE sem antes validar o resultado com SELECT usando a mesma cláusula WHERE.

Uso de LIMIT para limitar danos:

DELETE FROM logs_antigos 
WHERE data < '2023-01-01'
LIMIT 1000;  -- Remove no máximo 1000 registros por execução

Implementação de soft delete:

Em vez de excluir fisicamente, marque o registro como inativo:

-- Adicione uma coluna à tabela
ALTER TABLE usuarios ADD COLUMN ativo BOOLEAN DEFAULT TRUE;
ALTER TABLE usuarios ADD COLUMN deleted_at TIMESTAMP NULL;

-- "Exclusão" segura
UPDATE usuarios 
SET ativo = FALSE, deleted_at = NOW() 
WHERE id = 42;

-- Consultas ignoram registros "excluídos"
SELECT * FROM usuarios WHERE ativo = TRUE;

6. Tratamento de Erros e Restrições

Violação de FOREIGN KEY:

Se você tentar excluir um registro pai que ainda tem filhos, o banco lançará um erro:

ERROR: update or delete on table "clientes" violates foreign key constraint "pedidos_cliente_id_fkey" on table "pedidos"

Estratégias de integridade referencial:

Opção Comportamento
ON DELETE CASCADE Remove automaticamente os filhos
ON DELETE SET NULL Define a FK como NULL nos filhos
ON DELETE RESTRICT Impede a exclusão se houver filhos
ON DELETE NO ACTION Similar a RESTRICT, verifica no final

Lidando com timeouts em tabelas grandes:

-- Exclusão em lotes para evitar locks prolongados
WHILE (SELECT COUNT(*) FROM logs_antigos WHERE data < '2020-01-01') > 0
BEGIN
    DELETE TOP (1000) FROM logs_antigos WHERE data < '2020-01-01';
    WAITFOR DELAY '00:00:01';  -- Pausa de 1 segundo
END

7. Performance e Otimização

Impacto de índices:

Índices aceleram a localização dos registros a serem excluídos, mas cada exclusão também precisa atualizar os índices. Em tabelas com muitos índices, o DELETE pode ser mais lento.

Exclusão em lotes (batch delete):

Para tabelas volumosas, exclua em blocos menores:

-- Exclui 5000 registros por vez
DELETE FROM logs_auditoria
WHERE id IN (
    SELECT id FROM logs_auditoria 
    WHERE data < '2022-01-01' 
    LIMIT 5000
);

Comparação de performance:

Operação Tabela 10M registros Tabela 100M registros
DELETE (sem WHERE) 30-60 segundos 5-10 minutos
TRUNCATE < 1 segundo 1-3 segundos
DELETE com WHERE indexado Milissegundos Segundos
DELETE com WHERE não indexado Minutos Horas

Para grandes volumes de dados onde você precisa remover tudo, o TRUNCATE é drasticamente mais rápido. Use DELETE apenas quando precisar de filtragem seletiva ou da capacidade de rollback.

Referências