DELETE remove linhas, e só isso
O DELETE no SQLite serve para apagar linhas de uma tabela. Ele não derruba a tabela, não mexe no schema e não altera outras tabelas (a menos que você tenha configurado cascatas). A sintaxe é curta:
DELETE FROM users WHERE id = 2; localiza as linhas que batem com a condição e remove. As outras duas linhas continuam intactas. A tabela em si segue existindo — você pode continuar inserindo dados nela normalmente.
O modelo mental é simples: pense no DELETE como um SELECT que, em vez de retornar as linhas encontradas, joga elas fora.
O WHERE é quem faz o trabalho pesado
Todo DELETE sério depende totalmente da cláusula WHERE. Se você acertar, apaga exatamente o que queria. Se errar, apaga mais do que devia — às vezes a tabela inteira.
Os dois rascunhos não publicados, com zero visualizações, sumiram. As linhas publicadas continuam intactas porque a condição não bateu com elas. Você pode usar qualquer expressão que o WHERE aceite — IN, LIKE, BETWEEN, subconsultas, combinações de AND/OR.
Um hábito que vale a pena criar: antes de rodar um DELETE, execute o mesmo WHERE como um SELECT primeiro.
-- Visualize o que será removido:
SELECT * FROM posts WHERE published = 0 AND views = 0;
-- Satisfeito com as linhas? Agora exclua-as:
DELETE FROM posts WHERE published = 0 AND views = 0;
Esse passo a passo em duas etapas já salvou mais bancos de dados do que todas as ferramentas de backup juntas.
DELETE sem WHERE apaga a tabela inteira
Se você esquecer o WHERE, o DELETE apaga todas as linhas da tabela:
A tabela fica vazia, mas continua existindo. O SQLite não tem o comando TRUNCATE — o equivalente é DELETE FROM tabela;, e o próprio SQLite aplica internamente uma "otimização de truncate" que descarta todas as páginas de uma vez, em vez de apagar linha por linha. É rápido, mas continua sendo uma operação transacional, ou seja, dá pra fazer rollback.
Se você usou AUTOINCREMENT na chave primária, o contador não zera sozinho. Para os ids voltarem a começar do 1, é preciso limpar também a linha da sequência:
DELETE FROM log;
DELETE FROM sqlite_sequence WHERE name = 'log';
Para colunas INTEGER PRIMARY KEY simples (sem AUTOINCREMENT), o SQLite já reaproveita os ids livremente, então não precisa fazer isso.
Apagar várias linhas específicas no SQLite
O IN é a forma mais limpa de deletar um conjunto conhecido de linhas:
Também dá pra usar uma subquery no DELETE — muito útil quando as linhas que você quer apagar dependem de um join ou de outra tabela:
O SQLite não tem a sintaxe DELETE ... JOIN como o MySQL, mas dá para fazer a mesma coisa usando uma subconsulta no WHERE.
RETURNING: veja o que foi apagado
Use RETURNING no final do comando para receber as linhas removidas como resultado, igualzinho a um SELECT:
Você recebe de volta o id e o email de cada linha apagada. Isso é extremamente útil para:
- Registrar em log exatamente o que foi removido.
- Construir funcionalidades de desfazer (basta guardar as linhas retornadas em algum lugar).
- Confirmar, numa única ida ao banco, que o
DELETEafetou as linhas esperadas.
O RETURNING funciona com INSERT, UPDATE e DELETE. Há uma página dedicada só a ele com mais detalhes.
ON DELETE CASCADE em linhas relacionadas
Quando uma tabela pai e uma tabela filha estão ligadas por uma chave estrangeira, apagar o pai deixa filhos órfãos — a menos que você diga ao SQLite para fazer cascata:
Apagar o autor também apaga seus livros. Sem o ON DELETE CASCADE, esse mesmo DELETE ou seria executado deixando livros órfãos (se as foreign keys estiverem desligadas), ou falharia com erro de constraint (se estiverem ligadas).
A grande pegadinha: no SQLite, as foreign keys vêm desligadas por padrão. Você precisa rodar PRAGMA foreign_keys = ON; em toda conexão. Se esse pragma não for definido, o ON DELETE CASCADE é simplesmente ignorado em silêncio — e os livros continuam lá. A maioria dos drivers de aplicação já faz isso por você ou oferece uma opção; vale conferir o seu.
Outras opções de cascade que vale conhecer: ON DELETE SET NULL (zera a foreign key), ON DELETE RESTRICT (recusa o delete se houver filhos) e ON DELETE NO ACTION (o padrão — equivale ao RESTRICT na maioria dos casos).
DELETE com LIMIT (opção de compilação)
Algumas builds do SQLite suportam DELETE ... LIMIT, o que é útil para ir comendo uma tabela enorme em lotes:
DELETE FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000;
Isso exige que o SQLite tenha sido compilado com SQLITE_ENABLE_UPDATE_DELETE_LIMIT. Os binários oficiais e a maioria dos bindings de linguagens (o sqlite3 do Python, o better-sqlite3 do Node) já vêm com essa opção habilitada. Se a sua versão não tiver, você vai receber um erro de sintaxe — nesse caso, recorra a uma subconsulta:
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000
);
Deletes em lote mantêm as transações curtas, o que faz diferença quando outras conexões estão lendo o banco.
Envolva deletes grandes em uma transação
Todo DELETE já é transacional por natureza — ou todas as linhas que casam com o filtro vão embora, ou nenhuma vai. Mas quando você está prestes a apagar muita coisa, abrir uma transação explícita te dá a chance de dar ROLLBACK se algo parecer estranho:
ROLLBACK desfaz o delete por completo. Numa sessão real, você faria COMMIT quando a contagem batesse com o esperado. Transações também deixam o processo absurdamente mais rápido quando você precisa apagar muitas linhas uma de cada vez — envolver o loop num BEGIN/COMMIT evita um fsync a cada delete.
O que NÃO é apagado pelo DELETE
Vale destacar algumas confusões comuns:
DELETE FROM tabela;esvazia a tabela, mas não a remove. Para remover a própria tabela, useDROP TABLE tabela;.- O
DELETEnão diminui o tamanho do arquivo do banco. As páginas ficam marcadas como livres para reaproveitamento. Para recuperar espaço em disco, rodeVACUUM;(vamos falar disso no capítulo de performance). - Apagar uma linha não apaga as linhas filhas em outras tabelas, a menos que você tenha definido
ON DELETE CASCADEe as foreign keys estejam habilitadas. - Um
DELETEque não bate com nenhuma linha não é erro. É um comando bem-sucedido comchanges() = 0. Se precisar saber, confira a contagem de linhas afetadas.
A seguir: UPSERT
Muitas vezes você não quer realmente apagar — você quer inserir se a linha for nova, ou atualizar se ela já existir. No SQLite isso se chama UPSERT, e a cláusula ON CONFLICT resolve tudo em um único comando, em vez de três. É o nosso próximo assunto.
Perguntas frequentes
Como apagar uma linha no SQLite?
Use DELETE FROM nome_tabela WHERE condição;. É a cláusula WHERE que define quais linhas serão removidas. Por exemplo, DELETE FROM users WHERE id = 7; apaga apenas o usuário com id 7. Sem WHERE, todas as linhas da tabela vão embora — então é melhor checar duas vezes antes de rodar.
Como apago todas as linhas de uma tabela no SQLite?
Basta rodar DELETE FROM nome_tabela; sem WHERE. O SQLite não tem comando TRUNCATE — esse DELETE sem filtro faz o mesmo papel, e o próprio engine otimiza essa operação por dentro (a chamada "truncate optimization"). Se você também quer zerar os contadores de AUTOINCREMENT, apague depois os registros correspondentes em sqlite_sequence.
Dá para fazer cascade delete em tabelas relacionadas no SQLite?
Dá, desde que você declare ON DELETE CASCADE na foreign key e ative as foreign keys com PRAGMA foreign_keys = ON;. No SQLite elas vêm desligadas por padrão, e esse detalhe pega muita gente: sem o pragma, o cascade é simplesmente ignorado, sem nenhum aviso.
Como saber quais linhas foram apagadas?
Adicione um RETURNING no final: DELETE FROM users WHERE active = 0 RETURNING id, email; devolve as linhas removidas como se fosse um SELECT. Útil para gerar log, montar um "desfazer" ou simplesmente confirmar que você apagou exatamente o que pretendia.