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.

Você vai aprender
  • O que um UPDATE e um DELETE fazem quando não têm WHERE.
  • Por que as chaves estrangeiras às vezes salvam você, e quando não salvam.
  • Três jeitos de ter um WHERE e mesmo assim mudar tudo: id = 27 OR 28, o ; fora do lugar e a condição sempre verdadeira.
  • O ensaio: o SELECT com o mesmo WHERE, 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.
Aqui o acidente não custa nada

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.

ComandoCom WHERESem WHERE
SELECTMostra algumas linhasMostra todas (inofensivo)
UPDATEMuda algumas linhasMuda todas
DELETEApaga algumas linhasApaga 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:

  1. 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).
  2. O valor antigo sumiu. O banco não guarda o "antes". Se não houver backup, os 45 preços certos se perderam.
  3. 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.

PassoO que fazerPor quê
1. Escreva o WHERE primeiroNum SELECT: SELECT * FROM t WHERE …Um SELECT errado não estraga nada
2. Confira o resultadoAs linhas são as certas? A quantidade bate?É aqui que OR 28 e estoque >= 0 se revelam
3. Troque só o começoSELECT * FROM t → UPDATE t SET … ou DELETE FROM tO WHERE testado continua igual
4. Compare a contagem"N linhas afetadas" = linhas do ensaioSe não bater, pare e investigue
Comece pelo WHERE

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çãoComoProtege contra
Ler a contagem"N linhas afetadas", ou .changes on no terminalPerceber o acidente na hora
EnsaioSELECT com o mesmo WHERE, antesWHERE esquecido, OR 28, condição sempre verdadeira
WHERE pela chaveWHERE id = 3, WHERE id IN (27, 28)Mudar mais linhas do que as que você queria
RETURNINGNo fim do UPDATE/DELETEMudanças despercebidas (confirma depois)
TransaçãoBEGIN; … conferir … COMMIT; ou ROLLBACK;Qualquer engano, enquanto não houver COMMIT
Backup.backup arquivo.dbTudo, inclusive enganos já confirmados
Chaves estrangeirasPRAGMA foreign_keys = ON; no terminalApagar 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:

"45 linhas afetadas" quando você queria 1

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.

Uma mensagem de erro depois de "N linhas afetadas"

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 DELETE sem WHERE passou no terminal e não no editor

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.

Nos outros bancos

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

EscritaO 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 28Sempre 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 XO 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.dbCó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:

* FROM produtos WHERE estoque = 0;

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, o UPDATE muda todas as linhas e o DELETE apaga todas, sem erro e sem pergunta.
  • As chaves estrangeiras só barram o DELETE enquanto houver linhas apontando; no terminal, começam desligadas.
  • Um WHERE pode existir e não filtrar: id = 27 OR 28 (use IN (27, 28)), condição sempre verdadeira, ; antes do WHERE.
  • Ensaio: rode o SELECT com o mesmo WHERE antes; depois troque só o começo do comando e compare a contagem.
  • RETURNING mostra o que o comando mexeu. Ele confirma; não desfaz.
  • Para poder desfazer: BEGIN; antes e ROLLBACK; se der errado (aula Transações). Para tudo o mais: .backup.