SQL DELETE
A Rosângela Dias pediu para sair do cadastro da Loja Aurora. Três produtos nunca venderam e vão sair do catálogo. Um pedido foi cancelado. Em todos esses casos, a Ana precisa tirar linhas de uma tabela, e o comando para isso é o DELETE ("apagar"). Ele é o mais simples dos comandos que mudam dados, e o mais perigoso: uma linha apagada não volta. Nesta aula você vai ver como o WHERE escolhe o que sai, por que o banco às vezes se recusa a apagar, e como apagar um pedido inteiro na ordem certa.
- A forma
DELETE FROM tabela WHERE condição;e o que cada parte faz. - Como apagar uma linha, várias linhas, ou nenhuma ("0 linhas afetadas").
- Por que o SQLite responde
FOREIGN KEY constraint failedquando você tenta apagar um cliente que tem pedidos. - Como apagar um pedido e os seus itens, na ordem certa.
- A diferença entre apagar a linha (
DELETE) e apagar um valor (UPDATE … SET coluna = NULL). - O que acontece com o
idde uma linha apagada. RETURNING: ver exatamente o que foi apagado.
No editor do guia, cada Rodar começa do banco limpo: apague o que quiser, que no próximo Rodar está tudo de volta. Por isso os exemplos têm um SELECT depois do DELETE, na mesma caixa. No terminal, e em qualquer banco de verdade, não é assim: a seção 8 mostra.
A ideia: tirar a ficha do fichário
No fichário de papel, apagar um cliente é: achar a ficha, tirá-la da gaveta e jogá-la fora. A ficha sai inteira, com todos os campos. O DELETE é igual:
| No fichário | No SQL |
|---|---|
| Abrir a gaveta certa | DELETE FROM clientes |
| Achar a ficha (ou as fichas) que vão sair | WHERE id = 30 |
| Jogar fora a ficha inteira | A linha sai com todas as colunas |
| Apagar só um campo com a borracha | Não é DELETE: é UPDATE … SET campo = NULL |
1. O primeiro DELETE
A cliente 30, Rosângela Dias, pediu para sair do cadastro. Ela nunca fez um pedido.
DELETE FROM clientes WHERE id = 30; SELECT id, nome FROM clientes WHERE id > 26;
- DELETE FROM
- "Apagar de": vão sair linhas de uma tabela. O
FROMé obrigatório no SQLite. - clientes
- A tabela de onde as linhas saem.
- WHERE id = 30
- Quais linhas saem: só a do cliente 30. É o mesmo
WHEREdoSELECTe doUPDATE, com todas as condições que você já conhece. - ;
- Fim do comando. O
SELECTdepois é só para conferir.
O quadro mostra "1 linha afetada", e a lista termina no 29. Repare que o DELETE não tem lista de colunas: ele não apaga "o nome" ou "o email", apaga a linha inteira. Como no UPDATE, prefira escolher a linha pela chave primária (id): ela é única, então sai exatamente uma linha.
Experimente: o cliente 29, Elias Furtado, também pediu para sair (ele nunca comprou). Escreva o DELETE antes do SELECT e rode.
SELECT id, nome FROM clientes WHERE id > 26;
DELETE FROM clientes WHERE id = 29; SELECT id, nome FROM clientes WHERE id > 26;
2. Várias linhas de uma vez
Os produtos 43, 44 e 45 (o vaso, a tesoura de poda e as luvas de jardinagem) nunca foram vendidos e vão sair do catálogo. Um DELETE só, com IN (aula IN):
DELETE FROM produtos WHERE id IN (43, 44, 45); SELECT id, nome FROM produtos WHERE id > 40;
"3 linhas afetadas". O DELETE apaga todas as linhas que passam no WHERE, seja uma ou mil. Por isso, antes de um DELETE com uma condição mais larga que um id, faça o ensaio: rode um SELECT com o mesmo WHERE e veja quem vai sair.
SELECT id, nome FROM clientes WHERE id BETWEEN 27 AND 30;
São os quatro clientes que nunca pediram. Se a lista estiver certa, troque SELECT id, nome por DELETE e mantenha o resto igual. A aula O Perigo do UPDATE e DELETE sem WHERE mostra esse ensaio em detalhe.
3. Quando o banco se recusa a apagar
A Ana tenta apagar o cliente 1, José Ribamar Silva:
DELETE FROM clientes WHERE id = 1;
A mensagem: FOREIGN KEY constraint failed ("a restrição de chave estrangeira falhou"). O José tem pedidos: na tabela pedidos, a coluna cliente_id aponta para ele (é uma chave estrangeira, aula FOREIGN KEY). Se ele sumisse, esses pedidos ficariam apontando para um cliente que não existe. O banco protege os dados e recusa o DELETE; nada é apagado.
SELECT id, cliente_id, data FROM pedidos WHERE cliente_id = 1;
A mesma proteção vale para tudo o que está em uso na Aurora: uma categoria com produtos (DELETE FROM categorias WHERE id = 8 também dá FOREIGN KEY constraint failed), um produto que já foi vendido, um funcionário que atendeu pedidos. Na vida real, um cliente com histórico raramente é apagado: o comum é marcá-lo como inativo com um UPDATE, e o histórico de vendas continua completo.
4. Apagar um pedido inteiro: a ordem importa
O pedido 90 foi cancelado. Ele tem duas linhas em itens_pedido, que apontam para ele:
SELECT * FROM itens_pedido WHERE pedido_id = 90;
Se a Ana tentar apagar o pedido primeiro, é a mesma recusa da seção anterior: os itens ainda apontam para ele.
DELETE FROM pedidos WHERE id = 90;
A ordem certa é a contrária da do cadastro: no INSERT, primeiro o pedido, depois os itens; no DELETE, primeiro os itens, depois o pedido.
DELETE FROM itens_pedido WHERE pedido_id = 90; DELETE FROM pedidos WHERE id = 90; SELECT id, data FROM pedidos WHERE id > 87;
- O primeiro
DELETEtira os 2 itens do pedido 90. Repare noWHERE pedido_id = 90: na tabela de itens, quem identifica o pedido é a colunapedido_id. - Agora nada aponta para o pedido 90, e o segundo
DELETEo apaga. - O
SELECTmostra que a lista agora termina no 89.
Esses dois comandos precisam dar certo juntos: um pedido sem itens, ou itens sem pedido, deixam o banco pela metade. Num sistema de verdade, eles iriam dentro de uma transação, que garante o "tudo ou nada" entre vários comandos (aula Transações (BEGIN e COMMIT)). Também dá para pedir ao banco que apague os itens sozinho quando o pedido sair, com ON DELETE CASCADE, assunto da aula FOREIGN KEY.
5. DELETE apaga a linha; UPDATE apaga um valor
Uma confusão comum: querer "apagar o email" com DELETE.
DELETE email FROM clientes WHERE id = 7;
A mensagem: near "email": syntax error. O DELETE não aceita coluna: ele só sabe tirar linhas inteiras. Para apagar o valor de uma coluna e manter o cliente, use UPDATE:
UPDATE clientes SET email = NULL WHERE id = 6; SELECT id, nome, email FROM clientes WHERE id = 6;
| Você quer | Comando | Depois |
|---|---|---|
| Tirar o cliente do cadastro | DELETE FROM clientes WHERE id = 6; | A linha não existe mais |
| Apagar só o email | UPDATE clientes SET email = NULL WHERE id = 6; | O cliente continua, com email em branco |
6. "0 linhas afetadas" e o id que volta
Como no UPDATE, um DELETE que não acha nada não é erro:
DELETE FROM clientes WHERE nome = 'Rosangela Dias';
"0 linhas afetadas". A Ana escreveu "Rosangela" sem o acento; no banco, o nome é 'Rosângela Dias', e para o SQLite são textos diferentes. É mais um motivo para apagar pelo id, e para ler a contagem depois de todo DELETE.
E o número de uma linha apagada, fica vago para sempre? Veja o que acontece quando a Ana apaga o cliente 30 (o último) e cadastra outro logo depois:
DELETE FROM clientes WHERE id = 30;
INSERT INTO clientes (nome) VALUES ('Lívia Rocha');
SELECT id, nome FROM clientes WHERE id > 27;
A Lívia ganhou o 30, o número da Rosângela. Lembra da regra do INSERT? O id novo é "o maior que existe, mais 1"; depois do DELETE, o maior era 29. Se a linha apagada não for a última, o buraco fica: apagando o 28, o próximo cliente ganha o 31. Isso pode confundir um sistema que guardou o número antigo em algum lugar. Quando o número nunca pode ser reaproveitado, a tabela é criada com AUTOINCREMENT (aula AUTOINCREMENT).
7. RETURNING: ver o que foi apagado
Com RETURNING no fim, o DELETE devolve as linhas que acabou de apagar. É um registro do que saiu, e um jeito de conferir na hora:
DELETE FROM clientes WHERE id BETWEEN 27 AND 30 RETURNING id, nome;
Os quatro clientes que saíram. Se aparecesse alguém inesperado, você saberia na hora, e a aula O Perigo do UPDATE e DELETE sem WHERE mostra como juntar isso com uma transação para ainda poder voltar atrás.
DELETE FROM clientes;, sem WHERE, tenta apagar todos os clientes. Na Aurora, as chaves estrangeiras barram (quase todos têm pedidos), mas numa tabela sem essa proteção, todas as linhas vão embora, sem pergunta nenhuma. É tão importante que tem aula própria: O Perigo do UPDATE e DELETE sem WHERE. Quando o objetivo é mesmo esvaziar a tabela, veja Esvaziar uma Tabela.
8. No terminal, o DELETE é para valer
No terminal não existe "Rodar de novo": a linha apagada sai do arquivo loja.db. E tem um detalhe perigoso, que você já viu no INSERT: no programa sqlite3, a verificação das chaves estrangeiras começa desligada. Aquele DELETE do cliente 1, que o editor recusou, no terminal passa:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> DELETE FROM clientes WHERE id = 1; sqlite> SELECT id, cliente_id, data FROM pedidos WHERE cliente_id = 1; 1|1|2024-01-05 27|1|2024-10-29 53|1|2025-09-03 79|1|2026-06-29 « 4 pedidos de um cliente que não existe mais sqlite> PRAGMA foreign_key_check; pedidos|1|clientes|1 pedidos|27|clientes|1 pedidos|53|clientes|1 pedidos|79|clientes|1 sqlite> PRAGMA foreign_keys = ON; sqlite> DELETE FROM clientes WHERE id = 2; Runtime error: FOREIGN KEY constraint failed (19) sqlite> .quit
- O
DELETEdo cliente 1 não deu erro: com a verificação desligada, o SQLite não olhou para os pedidos. - Os quatro pedidos do José ficaram "órfãos": apontam para o cliente 1, que sumiu.
PRAGMA foreign_key_check;procura essas ligações quebradas: cada linha diz a tabela (pedidos), o número da linha órfã, a tabela para onde ela devia apontar (clientes) e, no fim, qual das chaves estrangeiras da tabela quebrou.- Depois de
PRAGMA foreign_keys = ON;, a proteção volta, e o cliente 2 não pode ser apagado.
Moral: no terminal, comece com PRAGMA foreign_keys = ON;, e faça um backup antes de apagar qualquer coisa importante.
Formas diferentes de tirar dados
| Você quer | Escreva |
|---|---|
| Apagar uma linha | DELETE FROM t WHERE id = 30; |
| Apagar um grupo de linhas | WHERE id IN (43, 44, 45), WHERE id BETWEEN 27 AND 30… |
| Conferir antes | SELECT … WHERE com o mesmo WHERE |
| Ver o que saiu | … RETURNING colunas; |
| Apagar só um valor | UPDATE t SET coluna = NULL WHERE …; |
| "Desativar" sem perder o histórico | Uma coluna como descontinuado e um UPDATE |
| Apagar o pai e os filhos | Filhos primeiro (itens), depois o pai (pedido); ou ON DELETE CASCADE |
| Poder desfazer | Uma transação: aula Transações |
| Esvaziar a tabela inteira | Aula Esvaziar uma Tabela |
Quando dá errado
DELETE FROM categorias WHERE id = 8;
A mensagem: FOREIGN KEY constraint failed. A categoria 8 (Jardim) tem produtos. A mensagem não diz quem está usando a linha; procure com um SELECT nas tabelas que apontam para ela (aqui, SELECT * FROM produtos WHERE categoria_id = 8).
Como consertar: apague (ou mude) primeiro as linhas que apontam para ela, ou desista de apagar e marque como inativa.
DELETE * FROM clientes WHERE id = 30;
A mensagem: near "*": syntax error. O DELETE não tem lista de colunas, nem *: a linha sai inteira sempre. Do mesmo jeito, DELETE email FROM … dá near "email": syntax error.
Como consertar: DELETE FROM clientes WHERE id = 30;
DELETE clientes WHERE id = 30;
A mensagem: near "clientes": syntax error. No SQLite, o FROM do DELETE é obrigatório. (No SQL Server ele é opcional, por isso você pode ver DELETE tabela WHERE … em exemplos de lá.)
DELETE FROM cliente WHERE id = 30;
A mensagem: no such table: cliente. A tabela é clientes, no plural.
DELETE FROM produtos WHERE id = 43, 44, 45;
A mensagem: near ",": syntax error. O = compara com um valor só.
Como consertar: WHERE id IN (43, 44, 45).
DELETE FROM pedidos WHERE entregue_em = NULL;
O que acontece: "0 linhas afetadas", sem erro, embora haja 5 pedidos sem entrega. É o = NULL de novo (aula O Valor NULL). Aqui o engano até "salvou" os pedidos; com IS NULL, o SQLite tentaria apagá-los e as chaves estrangeiras dos itens barrariam.
O DELETE FROM tabela WHERE condição é do SQL padrão e funciona igual no MySQL, no PostgreSQL e no SQL Server. Diferenças que valem saber:
- No SQL Server, o
FROMé opcional (DELETE clientes WHERE …), e o equivalente aoRETURNINGéOUTPUT DELETED.*. Trecho só ilustrativo:DELETE clientes OUTPUT DELETED.* WHERE id = 30;
- O PostgreSQL também tem
RETURNINGnoDELETE.
Referência rápida
| Escrita | O que faz |
|---|---|
DELETE FROM t WHERE id = 30; | Apaga a linha 30 |
DELETE FROM t WHERE id IN (43, 44, 45); | Apaga três linhas |
DELETE FROM t WHERE … RETURNING …; | Apaga e devolve as linhas apagadas |
DELETE FROM t; | Apaga todas as linhas (cuidado!) |
UPDATE t SET c = NULL WHERE …; | Apaga só um valor; a linha fica |
PRAGMA foreign_keys = ON; | Liga a proteção das chaves estrangeiras no terminal |
PRAGMA foreign_key_check; | Lista as ligações quebradas |
| Mensagem ou sintoma | Quer dizer |
|---|---|
FOREIGN KEY constraint failed | Outra tabela aponta para a linha; apague os "filhos" antes |
near "*": syntax error / near "email": syntax error | DELETE não tem lista de colunas |
near "clientes": syntax error | Faltou o FROM |
no such table: cliente | Nome da tabela errado |
| "0 linhas afetadas" | Nenhuma linha passou no WHERE (acento, = NULL, id errado) |
Exercícios
1. Complete o comando
2. Por que DELETE FROM clientes WHERE id = 1; dá FOREIGN KEY constraint failed no editor?
3. A Ana quer apagar só o email do cliente 6, sem tirá-lo do cadastro. Qual comando?
4. Para cancelar o pedido 90, em que ordem a Ana deve apagar?
5. A Ana apagou o cliente 30 (o último) e cadastrou um cliente novo logo depois. Que id ele ganhou?
Missão final: cancelar o pedido 89
Missão: o pedido 89 foi cancelado e precisa sair do banco, com os seus itens. A tentativa abaixo dá erro. Leia a mensagem e escreva os comandos na ordem certa. No fim, a tabela pedidos deve ficar com 89 linhas e a itens_pedido, com 218.
DELETE FROM pedidos WHERE id = 89; SELECT id, data FROM pedidos WHERE id > 86;
DELETE FROM itens_pedido WHERE pedido_id = 89; DELETE FROM pedidos WHERE id = 89; SELECT id, data FROM pedidos WHERE id > 86;
Resumo
DELETE FROM tabela WHERE condição;apaga as linhas que passam noWHERE, inteiras. Não tem lista de colunas.- Apague pela chave primária quando for uma linha só; para um grupo, faça antes o ensaio com
SELECTe o mesmoWHERE. FOREIGN KEY constraint failed: outra tabela aponta para a linha. Apague primeiro os "filhos" (itens), depois o "pai" (pedido).- Para apagar só um valor, use
UPDATE … SET coluna = NULL. - "0 linhas afetadas" não é erro. Leia sempre a contagem.
RETURNINGmostra o que saiu. - Sem
AUTOINCREMENT, oiddo último cliente apagado pode ser reaproveitado. - No terminal, o
DELETEé definitivo e as chaves estrangeiras começam desligadas: usePRAGMA foreign_keys = ON;.