SQL O Perigo do UPDATE e DELETE sem WHERE
Numa sexta-feira à tarde, a Ana quis baixar o preço do Lápis Grafite HB e digitou, com pressa, UPDATE produtos SET preco = 0.99;. Faltou o WHERE id = 3. O SQLite respondeu "45 linhas afetadas", e todos os produtos da Loja Aurora, da caneta ao jogo de panelas, passaram a custar 99 centavos. Nenhum erro, nenhuma pergunta "tem certeza?". Esta aula é sobre esse tipo de acidente: como ele acontece (inclusive de jeitos menos óbvios do que esquecer o WHERE) e os hábitos que protegem você dele.
- O que um
UPDATEe umDELETEfazem quando não têmWHERE. - Por que as chaves estrangeiras às vezes salvam você, e quando não salvam.
- Três jeitos de ter um
WHEREe mesmo assim mudar tudo:id = 27 OR 28, o;fora do lugar e a condição sempre verdadeira. - O ensaio: o
SELECTcom o mesmoWHERE, antes de mudar qualquer coisa. RETURNING: ver o que mudou, na hora.- As redes de segurança: transação, backup e o modo safe-updates do MySQL.
No editor do guia, cada Rodar começa do banco limpo. Você vai ver todos os desastres desta aula acontecerem de verdade, e no próximo Rodar a Loja Aurora volta inteira. No terminal e em qualquer banco de verdade, não há esse botão.
A ideia: a borracha que apaga o fichário inteiro
Lembre das aulas UPDATE e DELETE: o WHERE é quem escolhe as fichas que vão mudar ou sair. Sem ele, o banco não tem como escolher, e faz o que a frase diz ao pé da letra: UPDATE produtos SET preco = 0.99 quer dizer "o preço dos produtos passa a ser 0.99". Dos produtos, todos. É como pedir a alguém "risque o preço e escreva 0,99" e entregar o fichário inteiro, sem dizer qual ficha.
| Comando | Com WHERE | Sem WHERE |
|---|---|---|
SELECT | Mostra algumas linhas | Mostra todas (inofensivo) |
UPDATE | Muda algumas linhas | Muda todas |
DELETE | Apaga algumas linhas | Apaga todas |
1. UPDATE sem WHERE
O acidente da Ana, de verdade:
UPDATE produtos SET preco = 0.99; SELECT id, nome, preco FROM produtos;
"45 linhas afetadas", e a tabela inteira com o mesmo preço. Repare em três coisas:
- Nenhum erro. O comando é perfeitamente válido. Para o SQLite, "mudar o preço de todos" é um pedido normal (e às vezes é mesmo o que se quer).
- O valor antigo sumiu. O banco não guarda o "antes". Se não houver backup, os 45 preços certos se perderam.
- O número de linhas afetadas avisou. A Ana queria mudar 1 produto e o banco disse 45. Ler esse número é a primeira proteção.
Com um texto é a mesma coisa. Para corrigir o email de um cliente:
UPDATE clientes SET email = 'luis.brito@exemplo.com.br'; SELECT id, nome, email FROM clientes WHERE id <= 5;
Os 30 clientes agora têm o email do Luís Otávio. Uma campanha enviada depois disso mandaria 30 cópias para a mesma pessoa.
2. DELETE sem WHERE
Agora a Ana queria cancelar os itens do pedido 90 e esqueceu o WHERE pedido_id = 90:
DELETE FROM itens_pedido; SELECT * FROM itens_pedido;
"220 linhas afetadas", e a tabela ficou vazia: todos os itens de todos os 90 pedidos, apagados. A tabela continua existindo (ela não foi "derrubada"; isso seria DROP TABLE), mas sem nenhuma linha.
Quando as chaves estrangeiras salvam
Com outras tabelas, a Aurora tem uma proteção: as chaves estrangeiras (aula FOREIGN KEY). Um DELETE FROM produtos; tenta apagar produtos que estão em itens_pedido, e o banco recusa:
DELETE FROM produtos;
FOREIGN KEY constraint failed, e nenhum produto sai. Mas essa proteção depende de existirem "filhos" apontando para as linhas. Veja o que acontece se o primeiro acidente já tiver acontecido:
DELETE FROM itens_pedido; DELETE FROM produtos; SELECT * FROM produtos;
Com os itens apagados, nada mais aponta para os produtos, e os 45 vão embora também. As chaves estrangeiras protegem a ligação entre as tabelas, não os seus dados. E lembre: no terminal do sqlite3, elas começam desligadas (aula DELETE), e aí nem essa proteção existe.
3. Tem WHERE, e mesmo assim muda tudo
Esquecer o WHERE é o caso famoso. Os três abaixo são mais traiçoeiros, porque o WHERE está lá.
O OR pela metade
A Ana quer tornar VIP os clientes 27 e 28 e escreve como fala: "id igual a 27 ou 28".
UPDATE clientes SET vip = 1 WHERE id = 27 OR 28; SELECT id, nome, vip FROM clientes;
"30 linhas afetadas": todo mundo virou VIP. O SQLite leu o WHERE como duas condições, id = 27 ou 28. E 28, sozinho, é um número diferente de zero, que o SQLite trata como verdadeiro (aula OR). "Qualquer coisa OU verdadeiro" é sempre verdadeiro, então todas as linhas passaram. O certo é repetir a coluna, WHERE id = 27 OR id = 28, ou usar WHERE id IN (27, 28).
Missão: conserte o WHERE para que só os clientes 27 e 28 virem VIP. O SELECT lista os VIPs: a Aurora já tem 5, então devem aparecer 7.
UPDATE clientes SET vip = 1 WHERE id = 27 OR 28; SELECT id, nome, vip FROM clientes WHERE vip = 1;
UPDATE clientes SET vip = 1 WHERE id IN (27, 28); SELECT id, nome, vip FROM clientes WHERE vip = 1;
O ponto e vírgula fora do lugar
O ; termina o comando. Se ele cair antes do WHERE, o UPDATE acaba ali, sem condição nenhuma:
UPDATE produtos SET preco = 0.99; WHERE id = 3;
Olhe o quadro com atenção: primeiro "45 linhas afetadas", depois o erro near "WHERE": syntax error. A mensagem de erro é sobre a segunda linha, o WHERE id = 3; sozinho, que não é um comando válido. Mas a primeira já tinha rodado, sem WHERE, e mudado os 45 preços. Quem vê só a mensagem vermelha acha que "não rodou nada", e é o contrário.
Esse acidente também acontece em programas de banco de dados com um botão "executar seleção": a pessoa seleciona só a primeira linha do comando (UPDATE produtos SET preco = 0.99) e o programa roda exatamente isso.
A condição que é sempre verdadeira
Um WHERE pode existir e não filtrar nada. A Ana queria mudar só os produtos de estoque baixo, e escreveu a condição ao contrário:
UPDATE produtos SET descontinuado = 1 WHERE estoque >= 0;
"45 linhas afetadas": nenhum produto tem estoque negativo, então todos passaram. Para o SQLite, não há diferença entre isso e não ter WHERE. Só o ensaio, a seguir, mostra a diferença antes do estrago.
4. O ensaio: SELECT antes de mudar
O hábito mais importante desta aula: antes de um UPDATE ou DELETE, rode um SELECT com exatamente o mesmo WHERE. As linhas que ele mostrar são as que vão mudar. É como ensaiar a peça antes da estreia.
A Ana vai dar 10% de desconto nos Eletrônicos (categoria 2) com mais de 50 unidades em estoque. Primeiro, o ensaio:
SELECT id, nome, preco, estoque FROM produtos WHERE categoria_id = 2 AND estoque > 50;
Três produtos: o mouse, o carregador e o cabo HDMI. É isso mesmo que ela quer? Sim. Então, e só então, ela troca o começo e mantém o WHERE idêntico:
UPDATE produtos SET preco = ROUND(preco * 0.9, 2) WHERE categoria_id = 2 AND estoque > 50; SELECT id, nome, preco, estoque FROM produtos WHERE categoria_id = 2 AND estoque > 50;
"3 linhas afetadas", o mesmo número do ensaio. Se o UPDATE dissesse outro número, algo estaria errado.
| Passo | O que fazer | Por quê |
|---|---|---|
1. Escreva o WHERE primeiro | Num SELECT: SELECT * FROM t WHERE … | Um SELECT errado não estraga nada |
| 2. Confira o resultado | As linhas são as certas? A quantidade bate? | É aqui que OR 28 e estoque >= 0 se revelam |
| 3. Troque só o começo | SELECT * FROM t → UPDATE t SET … ou DELETE FROM t | O WHERE testado continua igual |
| 4. Compare a contagem | "N linhas afetadas" = linhas do ensaio | Se não bater, pare e investigue |
Um truque de quem trabalha com bancos de dados todo dia: ao escrever um UPDATE ou DELETE, escreva a linha do WHERE antes do resto. Assim, se você for interrompido no meio, o comando incompleto dá erro de sintaxe em vez de rodar sem filtro.
5. RETURNING: o que mudou, na hora
O SQLite aceita RETURNING no fim do UPDATE e do DELETE. Ele devolve as linhas que o comando mexeu: no UPDATE, já com os valores novos; no DELETE, como eram antes de sair. É o ensaio e a conferência num comando só:
UPDATE produtos SET preco = ROUND(preco * 0.9, 2) WHERE categoria_id = 2 AND estoque > 50 RETURNING id, nome, preco, estoque;
Se a Ana tivesse esquecido o WHERE, o RETURNING teria despejado 45 linhas na tela, impossível de não notar. Mas atenção: ele mostra o estrago depois que o estrago aconteceu. Ele não substitui o ensaio; ele confirma. Para poder voltar atrás, é preciso a próxima seção.
DELETE FROM itens_pedido WHERE pedido_id = 90 RETURNING pedido_id, produto_id, quantidade;
6. As redes de segurança
Transação: poder dizer "desfaz"
Uma transação é um grupo de comandos que só vale de verdade quando você confirma. Você abre com BEGIN ("começar"), faz as mudanças, confere, e no fim escolhe: COMMIT ("confirmar") ou ROLLBACK ("voltar atrás"). Veja a Ana repetindo o acidente no terminal, desta vez dentro de uma transação:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> BEGIN; sqlite> UPDATE produtos SET preco = 9.9; sqlite> SELECT id, nome, preco FROM produtos WHERE id <= 3; 1|Caderno Pautado 96 folhas|9.9 2|Caneta Esferográfica Azul|9.9 3|Lápis Grafite HB|9.9 « opa: todos iguais sqlite> ROLLBACK; sqlite> SELECT id, nome, preco FROM produtos WHERE id <= 3; 1|Caderno Pautado 96 folhas|12.35 2|Caneta Esferográfica Azul|2.79 3|Lápis Grafite HB|1.15 « os preços de antes voltaram sqlite> .quit
O ROLLBACK desfez o UPDATE inteiro, como se ele nunca tivesse acontecido. Como funcionam o BEGIN, o COMMIT e o ROLLBACK, e os cuidados que eles exigem, você aprende nas aulas Transações (BEGIN e COMMIT) e ROLLBACK e Savepoints. Por ora, guarde: antes de um UPDATE ou DELETE arriscado no terminal, comece com BEGIN;.
Backup: a cópia de antes
A rede mais simples de todas: uma cópia do banco antes da mudança. No terminal, o comando de ponto .backup grava uma cópia completa num arquivo novo (aula Backup (.dump e .backup)):
$ sqlite3 loja.db ".backup loja-antes.db" $ ls Área de trabalho Documentos Downloads Imagens loja-antes.db « a cópia loja.db Modelos Música Público Vídeos
Se o acidente acontecer, os dados de antes estão em loja-antes.db.
Formas diferentes de se proteger
| Proteção | Como | Protege contra |
|---|---|---|
| Ler a contagem | "N linhas afetadas", ou .changes on no terminal | Perceber o acidente na hora |
| Ensaio | SELECT com o mesmo WHERE, antes | WHERE esquecido, OR 28, condição sempre verdadeira |
| WHERE pela chave | WHERE id = 3, WHERE id IN (27, 28) | Mudar mais linhas do que as que você queria |
RETURNING | No fim do UPDATE/DELETE | Mudanças despercebidas (confirma depois) |
| Transação | BEGIN; … conferir … COMMIT; ou ROLLBACK; | Qualquer engano, enquanto não houver COMMIT |
| Backup | .backup arquivo.db | Tudo, inclusive enganos já confirmados |
| Chaves estrangeiras | PRAGMA foreign_keys = ON; no terminal | Apagar linhas que outras tabelas usam |
Quando dá errado
Nesta aula, "dar errado" quase nunca é uma mensagem vermelha: é o banco fazer, sem reclamar, algo que você não queria. Os sintomas:
O que aconteceu: o WHERE faltou, ou não filtrou (sempre verdadeiro, OR pela metade).
O que fazer: no editor, rode de novo: o banco volta limpo. No terminal, se você abriu uma transação, ROLLBACK;. Se não abriu, só um backup traz os dados de volta.
O que aconteceu: um ; no lugar errado dividiu o comando em dois. O primeiro pedaço rodou (sem WHERE); o erro é do segundo.
O que fazer: nunca conclua "deu erro, então não rodou". Leia tudo o que o editor (ou o terminal) mostrou, de cima para baixo.
O que aconteceu: no editor, as chaves estrangeiras barram DELETE FROM produtos;. No sqlite3, elas começam desligadas, e o mesmo comando apaga os 45 produtos, deixando os itens dos pedidos apontando para o nada.
O que fazer: PRAGMA foreign_keys = ON; no começo de toda sessão no terminal.
Nenhum banco pede confirmação antes de um UPDATE ou DELETE sem WHERE: o comando é válido em todos. O MySQL tem uma proteção opcional, o modo safe-updates: abrindo o programa mysql com --safe-updates (ou o divertido sinônimo --i-am-a-dummy, "sou um bobo"), ele liga a variável sql_safe_updates, e todo UPDATE ou DELETE que não filtrar por uma chave no WHERE (ou não tiver LIMIT) dá erro em vez de rodar. Trecho só ilustrativo, em MySQL:
SET sql_safe_updates = 1; UPDATE produtos SET preco = 0.99; -- recusado: sem chave no WHERE UPDATE produtos SET preco = 0.99 WHERE id = 3; -- aceito
O SQLite não tem esse modo. Nele, as proteções são as desta aula: o ensaio, o RETURNING, a transação e o backup. O RETURNING também existe no PostgreSQL; no SQL Server, o equivalente é o OUTPUT.
Referência rápida
| Escrita | O que faz |
|---|---|
UPDATE t SET c = v; | Muda todas as linhas |
DELETE FROM t; | Apaga todas as linhas (se as chaves estrangeiras deixarem) |
WHERE id = 27 OR 28 | Sempre verdadeiro: o 28 sozinho vale "verdadeiro" |
WHERE id IN (27, 28) | O jeito certo de escolher duas linhas |
SELECT … WHERE X e depois UPDATE … WHERE X | O ensaio: as linhas do SELECT são as que vão mudar |
… RETURNING colunas; | Mostra as linhas que o comando mexeu |
BEGIN; … ROLLBACK; | Desfaz tudo desde o BEGIN (aula Transações) |
.backup arquivo.db | Cópia do banco antes de mexer |
Exercícios
1. O que UPDATE produtos SET estoque = 0; faz na Loja Aurora?
2. Quantas linhas UPDATE clientes SET vip = 1 WHERE id = 5 OR 6; muda?
3. O editor mostrou "45 linhas afetadas" e, embaixo, near "WHERE": syntax error. O que aconteceu?
4. O ensaio
Antes de DELETE FROM produtos WHERE estoque = 0;, a Ana faz o ensaio. Complete a palavra que começa o comando:
5. Desfazer
A Ana abriu uma transação com BEGIN; e rodou um UPDATE errado. Que comando desfaz a mudança?
Missão final: o desconto com conferência
Missão: a Ana vai dar 10% de desconto (preco * 0.9) nos produtos de Eletrônicos (categoria_id = 2) que têm mais de 50 unidades em estoque. O ensaio já está feito abaixo. Escreva o UPDATE com o mesmo WHERE e termine com RETURNING para conferir: devem aparecer exatamente 3 produtos, com o preço novo. (Dica: apague o SELECT do ensaio depois de conferir, ou deixe-o antes do UPDATE.)
SELECT id, nome, preco, estoque FROM produtos WHERE categoria_id = 2 AND estoque > 50;
UPDATE produtos SET preco = ROUND(preco * 0.9, 2) WHERE categoria_id = 2 AND estoque > 50 RETURNING id, nome, preco, estoque;
Esqueceu o WHERE? O RETURNING mostrou 45 linhas, e a missão não passou. Escreveu só WHERE categoria_id = 2? Vieram 7. É exatamente para isso que a conferência serve.
Resumo
- Sem
WHERE, oUPDATEmuda todas as linhas e oDELETEapaga todas, sem erro e sem pergunta. - As chaves estrangeiras só barram o
DELETEenquanto houver linhas apontando; no terminal, começam desligadas. - Um
WHEREpode existir e não filtrar:id = 27 OR 28(useIN (27, 28)), condição sempre verdadeira,;antes doWHERE. - Ensaio: rode o
SELECTcom o mesmoWHEREantes; depois troque só o começo do comando e compare a contagem. RETURNINGmostra o que o comando mexeu. Ele confirma; não desfaz.- Para poder desfazer:
BEGIN;antes eROLLBACK;se der errado (aula Transações). Para tudo o mais:.backup.