Como o SQL que a Lina Pediu à IA Dobrou as Vendas do Mês Passado - O Hábito de Conferir pelo Número de Linhas
O que você vai conseguir fazer
- Conferir o resultado de um SQL escrito pela IA pelo número de linhas e pelo conteúdo de uma linha
- Encontrar por que um JOIN aumentou as linhas e corrigir com uma condição a mais ou somando antes
- Rodar COUNT(*) com o mesmo WHERE antes de um UPDATE para confirmar quantas linhas vão mudar
Pré-requisitos (leia estes primeiro)
Você pediu um SQL para a IA. Ele rodou sem erros, mas os números não batem. Já aconteceu com você? Neste artigo, o Veterano Linny e a Lina constroem um hábito: conferir com os próprios olhos o número de linhas e uma linha do resultado.
O que você vai aprender
- Por que um SQL que roda sem erros ainda pode responder a outra pergunta
- Como perceber um JOIN que aumentou as linhas, e duas formas de corrigir
- O hábito de contar com a mesma condição antes de um UPDATE, e uma conferência de 3 passos para respostas da IA
Introdução
Conclusão: Rodar sem erros e dar a resposta certa são coisas diferentes. Se as vendas parecem em dobro, conte as linhas primeiro.
1. O que Aconteceu? A Lina Rodou o SQL da IA do Jeito que Veio
Conclusão: O SQL que junta três tabelas devolveu 18 linhas em vez de uma linha por membro. Por isso o total cresceu.
Os comandos deste artigo não funcionam no terminalUm programa interativo que interpreta os comandos digitados e os executa. virtual do Penguin Gym Linux. Teste no terminal do seu próprio computador (Ubuntu). Primeiro, abra "Como criar os dados de treino gym.db (faça uma vez, antes de tudo)" abaixo e prepare o ambiente.
sqlite3 é um comando que permite digitar SQL no terminal.sqlite3 gym.db "SQL", o SQL roda uma vez no arquivo gym.db. O $ no começo da linha só marca onde você digita, então não o digite.Os dados de treino da academia da Lina, gym.db, têm três tabelas.
| Nome da tabela | O que ela guarda |
|---|---|
members |
A lista de membros (id / nome / cidade). 12 pessoas |
enrollments |
Registros dos planos contratados. Quando o membro troca de plano, a linha antiga recebe uma data de fim |
payments |
Registros de pagamento do mês passado (id do membro / valor) |
Como criar os dados de treino gym.db (faça uma vez, antes de tudo)
Primeiro, confira se o sqlite3 está disponível.
- No terminal, digite
sqlite3 --versione pressione Enter. Se aparecer um número como3.45.1, está tudo pronto (o número muda conforme o sistema) - Se aparecer
command not found, digitesudo apt install sqlite3. Osudoroda um comando com permissãoAs regras de acesso de leitura / escrita / execução definidas em um arquivo ou diretório. de administrador, e oapté o comando que instala programas no Ubuntu - Se pedir uma senha, digite a sua senha de login e pressione Enter. Os caracteres não aparecem na tela, e isso é normal. Se perguntar
[Y/n], digiteye pressione Enter - Digite
sqlite3 --versionde novo e confirme que um número aparece
Depois, crie o gym.db.
- Copie todo o código abaixo, da primeira linha até a última linha
EOF - Cole no terminal com
Ctrl+Shift+Vou com o botão direito do mouse e "Colar". OCtrl+Vnão cola no terminal - Da segunda linha em diante, aparece um
>no começo de cada linha. Ele indica que o terminal está esperando o resto, e não é um erro - Quando tudo até o último
EOFaparecer, pressione Enter. Se você voltar à linha com$, deu certo
Um arquivo chamado gym.db aparece no diretórioUm local que organiza arquivos. É a mesma ideia da "pasta" do Windows ou macOS. atual (diretório é o mesmo que pasta). Tudo de <<'EOF' até EOF é passado ao sqlite3 de uma vez.
sqlite3 gym.db <<'EOF' DROP TABLE IF EXISTS members; DROP TABLE IF EXISTS enrollments; DROP TABLE IF EXISTS payments; CREATE TABLE members (id INTEGER PRIMARY KEY, name TEXT, city TEXT); INSERT INTO members VALUES (1,'Pip','Tokyo'), (2,'Momo','Tokyo'), (3,'Kai','Sapporo'), (4,'Luna','Tokyo'), (5,'Nico','Tokyo'), (6,'Rio','Osaka'), (7,'Sora','Osaka'), (8,'Tama','Osaka'), (9,'Ana','Lisboa'), (10,'Ken','Porto'), (11,'Yuki','Lisboa'), (12,'Leo','Austin'); CREATE TABLE enrollments (member_id INTEGER, plan TEXT, ended_at TEXT); INSERT INTO enrollments VALUES (1,'student','2025-03-31'), (1,'basic','2026-03-31'), (1,'pro',NULL), (2,'basic',NULL), (3,'basic','2025-08-31'), (3,'student','2026-01-31'), (3,'pro',NULL), (4,'student',NULL), (5,'pro','2025-06-30'), (5,'basic','2026-02-28'), (5,'pro',NULL), (6,'basic',NULL), (7,'basic','2025-09-30'), (7,'pro','2026-04-30'), (7,'pro',NULL), (9,'basic',NULL), (10,'student',NULL), (11,'basic',NULL); CREATE TABLE payments (member_id INTEGER, amount INTEGER); INSERT INTO payments VALUES (1,4500), (2,3000), (3,4500), (4,2000), (5,4500), (6,3000), (7,4500), (9,3000), (10,2000), (11,3000); EOF
Não importa quantas vezes você rode isto, o resultado é sempre o mesmo estado inicial. As 3 primeiras linhas, DROP TABLE IF EXISTS, apagam as tabelas feitas antes e depois as criam de novo. Se você estragar os dados, rode isto de novo e tudo volta ao normal.
$ sqlite3 gym.db "SELECT m.name, e.plan, p.amount FROM members m JOIN enrollments e ON e.member_id = m.id JOIN payments p ON p.member_id = m.id;"
Pip|student|4500 Pip|basic|4500 Pip|pro|4500 Momo|basic|3000 Kai|basic|4500 Kai|student|4500 Kai|pro|4500 Luna|student|2000 Nico|pro|4500 Nico|basic|4500 Nico|pro|4500 Rio|basic|3000 Sora|basic|4500 Sora|pro|4500 Sora|pro|4500 Ana|basic|3000 Ken|student|2000 Yuki|basic|3000
| separa as colunas. Depois de SELECT você escreve as colunas que quer, e depois de FROM você escreve o nome da tabela.members m dá à tabela members o apelido curto m. Então m.name quer dizer "a coluna name de members".JOIN liga duas tabelas juntando as linhas que têm o mesmo id de membro. Depois de ON, você escreve a condição para ligá-las.SUM(p.amount) soma todos os números da coluna amount. Vamos somar com o SQL da IA.$ sqlite3 gym.db "SELECT SUM(p.amount) FROM members m JOIN enrollments e ON e.member_id = m.id JOIN payments p ON p.member_id = m.id;"
70000
$ sqlite3 gym.db "SELECT SUM(amount) FROM payments;"
34000
2. Conte Primeiro: 12 Membros, 18 Linhas
Conclusão: Uma lista com uma linha por membro nunca deveria ter mais linhas do que membros. 18 linhas para 12 pessoas mostram linhas a mais.
COUNT(*) conta quantas linhas existem.$ sqlite3 gym.db "SELECT COUNT(*) FROM members;"
12
$ sqlite3 gym.db "SELECT COUNT(*) FROM members m JOIN enrollments e ON e.member_id = m.id JOIN payments p ON p.member_id = m.id;"
18
WHERE mantém só as linhas que atendem a uma condição.id de membro do Pip é 1. O * em SELECT * quer dizer "todas as colunas".$ sqlite3 gym.db "SELECT * FROM enrollments WHERE member_id = 1;"
1|student|2025-03-31 1|basic|2026-03-31 1|pro|
JOIN cria uma linha para cada linha correspondente do outro lado. Por isso o pagamento de 4.500 ienes foi copiado em três linhas.Uma relação em que "uma pessoa tem vários registros" se chama um para muitos. Quando você faz JOIN com uma tabela um para muitos, o resultado ganha tantas linhas quanto o lado "muitos" tiver.
3. Duas Correções: Manter Só as Linhas Atuais / Somar Antes
Conclusão: Mantenha só as linhas do plano atual, ou some por membro antes de juntar. Os dois jeitos dão 10 linhas e 34.000 ienes.
Correção 1: manter só as linhas do plano atual
ended_at. A linha do plano atual está vazia. Em SQL, esse estado vazio se chama NULL.sqlite3 não mostra nada para NULL. O final vazio de 1|pro|, que vimos antes, é um NULL.IS NULL, e não = NULL. Vamos digitar os dois e comparar.$ sqlite3 gym.db "SELECT COUNT(*) FROM enrollments WHERE ended_at = NULL;"
0
$ sqlite3 gym.db "SELECT COUNT(*) FROM enrollments WHERE ended_at IS NULL;"
10
= NULL dá 0 linhas, e com IS NULL dá 10 linhas. Não aparece erro, mas os resultados são bem diferentes.WHERE e.ended_at IS NULL ao SQL da IA.$ sqlite3 gym.db "SELECT m.name, e.plan, p.amount FROM members m JOIN enrollments e ON e.member_id = m.id JOIN payments p ON p.member_id = m.id WHERE e.ended_at IS NULL;"
Pip|pro|4500 Momo|basic|3000 Kai|pro|4500 Luna|student|2000 Nico|pro|4500 Rio|basic|3000 Sora|pro|4500 Ana|basic|3000 Ken|student|2000 Yuki|basic|3000
Correção 2: somar antes, depois juntar
payments por membro, e depois ligue os nomes. Agrupar dados somando ou contando se chama "agregar".$ sqlite3 gym.db "SELECT m.name, t.total
FROM members m
JOIN (SELECT member_id, SUM(amount) AS total
FROM payments GROUP BY member_id) t
ON t.member_id = m.id;"Pip|4500 Momo|3000 Kai|4500 Luna|2000 Nico|4500 Rio|3000 Sora|4500 Ana|3000 Ken|2000 Yuki|3000
GROUP BY member_id quer dizer "agrupar por membro". Como ligamos uma linha a uma linha, nenhuma linha é acrescentada.AS total dá o nome total à coluna somada. O t depois dos parênteses é o nome da tabela montada dentro deles.ended_at está vazio)".4. Conte Antes de Mudar: um UPDATE sem WHERE
Conclusão: Antes de rodar o UPDATE, rode COUNT(*) com o mesmo WHERE. Se só uma linha deve mudar, confirme que aparece 1 antes de rodar.
UPDATE members
SET city = 'Osaka'
WHERE id = 3;
UPDATE é o comando que reescreve o conteúdo de uma tabela. Depois de SET você escreve o novo valor, e depois de WHERE a condição de quais linhas reescrever.Os dados reescritos pelo UPDATE não vão para a lixeira. Não dá para desfazer como o "Desfazer" do computador. Tudo é salvo no momento em que roda. Antes de testar, faça uma cópia com cp (o comando que copia arquivos) para ficar seguro. O gym.db é um arquivo feito só para treino, então quebrá-lo não afeta nenhum outro arquivo do seu computador. Teste um UPDATE sem WHERE só neste gym.db de treino. Nunca teste em um banco de dados do trabalho ou em arquivos de outras pessoas.
gym.db é de treino, então não tem problema se quebrar.cp se escreve na ordem cp origem destino. Se já existir um arquivo com o mesmo nome no destino, ele é sobrescrito sem perguntar, então tome cuidado.SELECT changes(); no final. changes() devolve o número de linhas alteradas pelo comando anterior.$ cp gym.db gym-backup.db
WHERE id = 3;.$ sqlite3 gym.db "UPDATE members SET city = 'Osaka'; SELECT changes();"
12
$ sqlite3 gym.db "SELECT city, COUNT(*) FROM members GROUP BY city;"
Osaka|12
UPDATE sem WHERE reescreve todas as linhas da tabela. Desta vez temos a cópia, então vamos voltar ao original.-i. Ele pergunta overwrite? antes de sobrescrever, então digite y e pressione Enter. A pergunta pode aparecer em português, conforme o idioma do sistema.$ cp -i gym-backup.db gym.db
cp: overwrite 'gym.db'? y
UPDATE, conte com o mesmo WHERE. Se você quer mudar só o Kai, a resposta deve ser 1.$ sqlite3 gym.db "SELECT COUNT(*) FROM members WHERE id = 3;"
1
$ sqlite3 gym.db "SELECT * FROM members WHERE id = 3;"
3|Kai|Sapporo
$ sqlite3 gym.db "UPDATE members SET city = 'Osaka' WHERE id = 3; SELECT changes();"
1
5. Uma Conferência de 3 Passos ao Pedir para a IA
Conclusão: Preveja o número de linhas, conte com COUNT(*) e olhe uma linha. Esses 3 passos pegam resultados com linhas demais e de menos.
| Passo | O que fazer | Exemplo de hoje |
|---|---|---|
| 1 | Dizer antes quantas linhas deveriam vir | Por membro: 12 linhas ou menos. Só o Kai: 1 linha |
| 2 | Contar as linhas reais com COUNT(*) |
18 linhas é demais. Antes do UPDATE, use o mesmo WHERE |
| 3 | Pegar uma linha e olhar o conteúdo | As linhas do Pip mostraram três registros de plano |
$ sqlite3 gym.db "SELECT COUNT(*) FROM members WHERE city = 'Tokyo';"
4
$ sqlite3 gym.db "SELECT name FROM members WHERE city = 'Tokyo';"
Pip Momo Luna Nico
Sao Paulo, sem acento, city = 'São Paulo' não encontraria essa pessoa.$ sqlite3 gym.db "SELECT 'Sao Paulo' = 'São Paulo', 'São Paulo' = 'São Paulo';"
0|1
0 quer dizer "diferente", e 1 quer dizer "igual". Mesmo parecidas, uma letra com acento e outra sem acento são comparadas como caracteres diferentes. Então, mesmo que a contagem venha menor, nenhum erro aparece.Aqui estão os tropeços mais comuns numa tabela.
| Tropeço | O que acontece | Como conferir |
|---|---|---|
Fazer JOIN com uma tabela um para muitos |
As linhas aumentam e o total cresce | Comparar o número de linhas com o de membros |
Procurar linhas vazias com = NULL |
Nenhuma linha é escolhida | Escrever IS NULL |
Esquecer o WHERE no UPDATE |
Todas as linhas são reescritas | Rodar antes COUNT(*) com o mesmo WHERE |
| Palavras com e sem acento misturadas | Linhas que deveriam vir ficam de fora | Comparar com a previsão e olhar as linhas |
Aparece no such table: members |
Você está em um diretório diferente de onde criou o gym.db. O sqlite3 cria em silêncio um arquivo novo e vazio quando ele não existe |
Confira com pwd (mostra onde você está) e volte com cd (muda de diretório) |
Mini Exercícios
Conclusão: Teste a conferência de 3 passos no gym.db de treino. Conte, olhe uma linha e decida você mesmo se está certo.
gym.db de treino. Você pode continuar usando o gym.db deste artigo.Exercício 1: quantos membros estão no plano pro agora?
Tarefa: Você pediu para a IA "o número de membros que estão no plano pro agora", e ela devolveu SELECT COUNT(*) FROM enrollments WHERE plan = 'pro';. O resultado é 6. Confira se está certo.
Ver Dica 1 (Direção)
Olhe as linhas do pro uma por uma. Há linhas de planos encerrados misturadas?
Ver Dica 2 (Nome do comando)
No sqlite3, rode SELECT member_id, plan, ended_at FROM enrollments WHERE plan = 'pro'; e olhe a coluna ended_at. Para manter só os planos atuais, acrescente AND ended_at IS NULL. AND quer dizer "só as linhas que atendem às duas condições".
Ver Resposta
$ sqlite3 gym.db "SELECT member_id, plan, ended_at FROM enrollments WHERE plan = 'pro';"
1|pro| 3|pro| 5|pro|2025-06-30 5|pro| 7|pro|2026-04-30 7|pro|
Há duas linhas de pro encerrado misturadas. Contando só as linhas de pro atual, são 4 pessoas.
$ sqlite3 gym.db "SELECT COUNT(*) FROM enrollments WHERE plan = 'pro' AND ended_at IS NULL;"
4
Exercício 2: conte antes de mudar
Tarefa: A Luna se mudou para Osaka. Antes de mudar a cidade dela para Osaka, confirme que só uma linha vai mudar, e depois rode.
Ver Dica 1 (Direção)
Antes de reescrever, use a mesma condição para ver "quantas linhas existem" e "o que essa linha contém".
Ver Dica 2 (Nome do comando)
Conte com SELECT COUNT(*) FROM members WHERE name = 'Luna'; e veja o conteúdo com SELECT *. Por fim, rode o UPDATE e SELECT changes();.
Ver Resposta
Primeiro, faça uma cópia por precaução.
$ cp gym.db gym-backup.db
$ sqlite3 gym.db "SELECT COUNT(*) FROM members WHERE name = 'Luna';"
1
$ sqlite3 gym.db "SELECT * FROM members WHERE name = 'Luna';"
4|Luna|Tokyo
$ sqlite3 gym.db "UPDATE members SET city = 'Osaka' WHERE name = 'Luna'; SELECT changes();"
1
Revisão
Conclusão: Não fique desconfiando do SQL da IA. Olhe o número de linhas e uma linha para decidir se está certo. Se estiver, use com segurança.
UPDATE que esquece o WHERE reescreve todas as linhas sem nenhum erro. Por isso você conta antes de mudar.Resumo em 3 Linhas
Conclusão: Um JOIN que aumenta linhas, um UPDATE que esquece o WHERE e grafias diferentes. Todos aparecem quando você conta as linhas.
- Desconfie quando houver mais linhas do que membros - juntar uma tabela um para muitos aumenta as linhas e infla o total
- Conte antes de mudar - antes do
UPDATE, rodeCOUNT(*)com o mesmoWHERE - Prever, contar, olhar uma linha - confira o SQL da IA com esses 3 passos