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.

Você vai aprender
  • 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 failed quando 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 id de uma linha apagada.
  • RETURNING: ver exatamente o que foi apagado.
Pode apagar sem medo (aqui)

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árioNo SQL
Abrir a gaveta certaDELETE FROM clientes
Achar a ficha (ou as fichas) que vão sairWHERE id = 30
Jogar fora a ficha inteiraA linha sai com todas as colunas
Apagar só um campo com a borrachaNã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 WHERE do SELECT e do UPDATE, com todas as condições que você já conhece.
;
Fim do comando. O SELECT depois é 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;
  1. O primeiro DELETE tira os 2 itens do pedido 90. Repare no WHERE pedido_id = 90: na tabela de itens, quem identifica o pedido é a coluna pedido_id.
  2. Agora nada aponta para o pedido 90, e o segundo DELETE o apaga.
  3. O SELECT mostra 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ê querComandoDepois
Tirar o cliente do cadastroDELETE FROM clientes WHERE id = 6;A linha não existe mais
Apagar só o emailUPDATE 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.

E se esquecer o WHERE?

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
  1. O DELETE do cliente 1 não deu erro: com a verificação desligada, o SQLite não olhou para os pedidos.
  2. Os quatro pedidos do José ficaram "órfãos": apontam para o cliente 1, que sumiu.
  3. 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.
  4. 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ê querEscreva
Apagar uma linhaDELETE FROM t WHERE id = 30;
Apagar um grupo de linhasWHERE id IN (43, 44, 45), WHERE id BETWEEN 27 AND 30…
Conferir antesSELECT … WHERE com o mesmo WHERE
Ver o que saiu… RETURNING colunas;
Apagar só um valorUPDATE t SET coluna = NULL WHERE …;
"Desativar" sem perder o históricoUma coluna como descontinuado e um UPDATE
Apagar o pai e os filhosFilhos primeiro (itens), depois o pai (pedido); ou ON DELETE CASCADE
Poder desfazerUma transação: aula Transações
Esvaziar a tabela inteiraAula Esvaziar uma Tabela

Quando dá errado

Apagou algo que está em uso
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.

Escreveu como num SELECT
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;

Esqueceu o FROM
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á.)

Tabela com o nome errado
DELETE FROM cliente
WHERE id = 30;

A mensagem: no such table: cliente. A tabela é clientes, no plural.

Vários ids com vírgula solta
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).

O WHERE não achou ninguém
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.

Nos outros bancos

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 ao RETURNING é OUTPUT DELETED.*. Trecho só ilustrativo:
    DELETE clientes
    OUTPUT DELETED.*
    WHERE id = 30;
    
  • O PostgreSQL também tem RETURNING no DELETE.

Referência rápida

EscritaO 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 sintomaQuer dizer
FOREIGN KEY constraint failedOutra tabela aponta para a linha; apague os "filhos" antes
near "*": syntax error / near "email": syntax errorDELETE não tem lista de colunas
near "clientes": syntax errorFaltou o FROM
no such table: clienteNome da tabela errado
"0 linhas afetadas"Nenhuma linha passou no WHERE (acento, = NULL, id errado)

Exercícios

1. Complete o comando

produtos id = 45;

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 no WHERE, 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 SELECT e o mesmo WHERE.
  • 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. RETURNING mostra o que saiu.
  • Sem AUTOINCREMENT, o id do último cliente apagado pode ser reaproveitado.
  • No terminal, o DELETE é definitivo e as chaves estrangeiras começam desligadas: use PRAGMA foreign_keys = ON;.