Menu

SQLite DELETE: como apagar linhas com WHERE e RETURNING

Veja como o DELETE funciona no SQLite: escrever um WHERE seguro, apagar todas as linhas, fazer cascade em tabelas relacionadas e recuperar o que foi apagado com RETURNING.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

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 DELETE afetou 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, use DROP TABLE tabela;.
  • O DELETE não diminui o tamanho do arquivo do banco. As páginas ficam marcadas como livres para reaproveitamento. Para recuperar espaço em disco, rode VACUUM; (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 CASCADE e as foreign keys estejam habilitadas.
  • Um DELETE que não bate com nenhuma linha não é erro. É um comando bem-sucedido com changes() = 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR