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

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.

Lina: Veterano Linny, estou com um problema. Pedi para a IA: "Liste o plano de cada membro e quanto cada um pagou no mês passado". Somei os valores do SQL que ela devolveu, e deu 70.000 ienes.
Veterano Linny: E quanto diz o registro do caixa para as vendas do mês passado?
Lina: 34.000 ienes. É quase o dobro. E não apareceu nenhum erro...
Veterano Linny: Rodar sem erros e dar a resposta certa são coisas diferentes. Hoje, vamos ver juntos como conferir contando.

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.

Veterano Linny: Vamos começar com algumas palavras. Um banco de dados é um lugar que guarda dados em forma de tabelas.
Veterano Linny: As tabelas são o que o SQL usa. Um registro na horizontal é uma "linha", e cada item na vertical é uma "coluna".
Veterano Linny: SQL é a linguagem que você usa para pedir ao banco de dados: "traga os dados que atendem a esta condição". O sqlite3 é um comando que permite digitar SQL no terminal.
Veterano Linny: Se você digitar 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.

  1. No terminal, digite sqlite3 --version e pressione Enter. Se aparecer um número como 3.45.1, está tudo pronto (o número muda conforme o sistema)
  2. Se aparecer command not found, digite sudo apt install sqlite3. O sudo roda um comando com permissãoAs regras de acesso de leitura / escrita / execução definidas em um arquivo ou diretório. de administrador, e o apt é o comando que instala programas no Ubuntu
  3. 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], digite y e pressione Enter
  4. Digite sqlite3 --version de novo e confirme que um número aparece

Depois, crie o gym.db.

  1. Copie todo o código abaixo, da primeira linha até a última linha EOF
  2. Cole no terminal com Ctrl+Shift+V ou com o botão direito do mouse e "Colar". O Ctrl+V não cola no terminal
  3. 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
  4. Quando tudo até o último EOF aparecer, 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.

Lina: Pedi para a IA: "Liste o plano de cada membro e quanto cada um pagou no mês passado". Este foi o SQL que ela me deu.
$ 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
Veterano Linny: Na saída, cada linha é um registro, e o | separa as colunas. Depois de SELECT você escreve as colunas que quer, e depois de FROM você escreve o nome da tabela.
Veterano Linny: members m dá à tabela members o apelido curto m. Então m.name quer dizer "a coluna name de members".
Veterano Linny: O 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.
Lina: Somei a coluna de valores numa planilha e deu 70.000 ienes.
Veterano Linny: 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
Lina: 70.000 ienes, igual à planilha. O que acontece se eu somar só os registros de pagamento?
$ sqlite3 gym.db "SELECT SUM(amount) FROM payments;"
34000
Lina: 34.000 ienes. Igual ao registro do caixa. Então onde foi que dobrou?

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.

Veterano Linny: Antes de ler o conteúdo, vamos contar. 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
Lina: São 12 membros, mas o resultado tem 18 linhas... Com uma linha por pessoa, não teria como passar de 12.
Veterano Linny: Exatamente. Agora vamos pegar uma pessoa só e olhar com os próprios olhos. O WHERE mantém só as linhas que atendem a uma condição.
Veterano Linny: 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|
Lina: O Pip tem três registros de plano. As linhas student e basic têm data de fim.
Veterano Linny: A linha pro termina vazia. Ela ainda não tem data de fim, ou seja, é o plano atual.
Veterano Linny: O Pip trocou de plano duas vezes. O 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.
Lina: Então, se uma pessoa tem vários registros, juntar as tabelas aumenta as linhas. Foi daí que veio o dobro.

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

Veterano Linny: As linhas de planos encerrados têm uma data em ended_at. A linha do plano atual está vazia. Em SQL, esse estado vazio se chama NULL.
Veterano Linny: O sqlite3 não mostra nada para NULL. O final vazio de 1|pro|, que vimos antes, é um NULL.
Veterano Linny: Para escolher linhas vazias, escreva 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
Lina: Com = NULL dá 0 linhas, e com IS NULL dá 10 linhas. Não aparece erro, mas os resultados são bem diferentes.
Veterano Linny: Isso mesmo. Agora vamos acrescentar 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
Lina: Agora são 10 linhas. A soma dá 34.000 ienes. Mas são 12 membros, não são?
Veterano Linny: A Tama e o Leo não estão em nenhum plano agora. Eles também não têm pagamento, então não há nada para ligar a eles. 10 de 12 faz sentido como número de linhas.

Correção 2: somar antes, depois juntar

Veterano Linny: Se você não precisa do nome do plano, há outra correção. Primeiro some 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
Veterano Linny: Dentro dos parênteses, primeiro montamos uma tabela com uma linha por membro. GROUP BY member_id quer dizer "agrupar por membro". Como ligamos uma linha a uma linha, nenhuma linha é acrescentada.
Veterano Linny: AS total dá o nome total à coluna somada. O t depois dos parênteses é o nome da tabela montada dentro deles.
Lina: Esta também dá 10 linhas e 34.000 ienes. ...Espera, então o SQL da IA não estava errado?
Veterano Linny: A IA ligou "planos e pagamentos" para todos os registros que existiam. A sua pergunta não dizia "plano atual", certo?
Lina: É verdade... Minha pergunta estava vaga. Da próxima vez, vou pedir "só o plano atual (linhas em que 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.

Lina: O Kai se mudou de Sapporo para Osaka, então pedi para a IA um "SQL que muda a cidade do Kai para Osaka". Veio isto.
UPDATE members
SET city = 'Osaka'
WHERE id = 3;
Veterano Linny: 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.
Veterano Linny: Antes de rodar, vamos fazer uma cópia por precaução. Este gym.db é de treino, então não tem problema se quebrar.
Veterano Linny: O 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.
Veterano Linny: Quando rodar, acrescente SELECT changes(); no final. changes() devolve o número de linhas alteradas pelo comando anterior.
$ cp gym.db gym-backup.db
Lina: Certo, colando o SQL e rodando... Ah, rodei sem a última linha, 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
Lina: O quê? Agora as 12 pessoas estão em Osaka. E não apareceu nenhum erro...
Veterano Linny: Mudaram 12 linhas. Um UPDATE sem WHERE reescreve todas as linhas da tabela. Desta vez temos a cópia, então vamos voltar ao original.
Veterano Linny: Para voltar, acrescente -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
Veterano Linny: Se você esqueceu de fazer a cópia, rode de novo os passos de "Como criar os dados de treino gym.db". Tudo volta ao estado inicial.
Veterano Linny: Este é o jeito certo. Antes de rodar o 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
Lina: Uma linha, e é o Kai. Agora que conferi até aqui, vou rodar.
$ sqlite3 gym.db "UPDATE members SET city = 'Osaka' WHERE id = 3; SELECT changes();"
1
Lina: Só uma linha mudou. Desta vez, o SQL da IA estava certo.
Veterano Linny: Isso. Confirmar que está certo também é um resultado importante. Você conta não para desconfiar, mas para rodar com segurança.

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.

Lina: Será que eu devo parar de pedir para a IA?
Veterano Linny: Não precisa parar. Basta acrescentar o hábito de olhar o "número de linhas" e "uma linha" do que volta. Vamos organizar em 3 passos.
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
Lina: Vou testar. Pela lista de membros, deveria haver 4 membros em Tokyo. Pedi para a IA "o número de membros em Tokyo".
$ 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
Lina: 4 pessoas, como eu previ, e os nomes batem com a lista. Este está certo.
Veterano Linny: Isso. Por outro lado, a contagem também pode vir pequena demais. Por exemplo, se a cidade de alguém estivesse gravada como 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
Veterano Linny: 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.
Lina: Com linhas demais ou de menos, dá para perceber comparando com o número que eu previ antes.

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.

Veterano Linny: Vamos testar a conferência de 3 passos no 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.

Lina: As vendas dobraram porque juntei registros em que uma pessoa tem várias linhas. Quando a pergunta é vaga, a IA escreve o SQL para outra leitura dela. Mesmo com uma pergunta mais clara, no final eu confiro o número de linhas e uma linha.
Veterano Linny: Isso mesmo. E um UPDATE que esquece o WHERE reescreve todas as linhas sem nenhum erro. Por isso você conta antes de mudar.
Lina: Prever, contar e olhar uma linha. Assim consigo conferir sozinha, mesmo pedindo ajuda para a IA.
Veterano Linny: Com esses 3 passos, você também pode usar a resposta da IA com segurança quando ela está certa. Essa é a parte mais importante.

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.

  1. Desconfie quando houver mais linhas do que membros - juntar uma tabela um para muitos aumenta as linhas e infla o total
  2. Conte antes de mudar - antes do UPDATE, rode COUNT(*) com o mesmo WHERE
  3. Prever, contar, olhar uma linha - confira o SQL da IA com esses 3 passos

Próximos Passos

Conclusão: Contar linhas funciona do mesmo jeito que o comando wc do Linux. Pratique contar e conferir no terminal também.

Compartilhe este artigo

Próximo passo