GROUP BY agrupa linhas em "baldes"
Funções agregadas como COUNT, SUM e AVG reduzem várias linhas a um único número. O GROUP BY permite fazer isso por categoria — um número por cliente, por mês, por status. Cada valor único (ou combinação de valores) vira uma linha no resultado.
Três clientes, três linhas no resultado. As seis linhas originais sumiram — foram agrupadas em "baldes" por cliente, com COUNT(*) e SUM(amount) calculados dentro de cada um.
O modelo mental é esse: GROUP BY customer diz "trate todas as linhas com o mesmo cliente como um único grupo". As funções agregadas então atuam separadamente sobre cada grupo.
O que pode entrar no SELECT junto com GROUP BY
É aqui que muita gente tropeça. Quando você usa GROUP BY, toda coluna que aparece no SELECT precisa estar ou no próprio GROUP BY, ou dentro de uma função agregada. Caso contrário, o valor fica ambíguo — afinal, de qual linha do grupo ele deveria vir?
Se você escrevesse SELECT region, rep, SUM(amount) com GROUP BY region, o SQLite executaria sem reclamar (ele é mais permissivo que outros bancos, que rejeitariam isso na hora), mas o rep seria escolhido de forma arbitrária dentro do grupo. Você acabaria com um nome de representante por região, sem nenhuma garantia de qual seria. Não confie nesse comportamento — agrupe por todas as colunas não agregadas que aparecem no SELECT.
HAVING: filtrando grupos depois da agregação
A diferença entre WHERE e HAVING é simples: WHERE filtra linhas antes do agrupamento, e HAVING filtra grupos depois do agrupamento. É só isso. E é justamente por isso que você não consegue colocar COUNT(*) > 1 dentro de um WHERE — na hora em que o WHERE roda, a contagem ainda nem existe.
Cleo só fez um pedido, então o grupo dela é eliminado pelo filtro. Sobram Ada e Boris. A condição é avaliada sobre o valor agregado de cada grupo, e não linha por linha.
No SQLite, dá para usar os apelidos (aliases) das colunas do SELECT diretamente no HAVING:
Geralmente, isso fica mais legível do que repetir SUM(amount) na cláusula HAVING.
Diferença entre WHERE e HAVING: usando os dois juntos
As duas cláusulas não são excludentes. O WHERE filtra quais linhas vão participar do agrupamento; o HAVING filtra quais grupos aparecem no resultado final. Na prática, a maioria das consultas usa as duas.
Leia de cima para baixo, na ordem em que o SQLite executa:
WHERE status = 'paid'— descarta de cara as linhas com reembolso.GROUP BY customer— agrupa o que sobrou por cliente.SUM(amount)é calculado dentro de cada grupo.HAVING SUM(amount) > 75— mantém só os grupos que passam no filtro.
Boris (80 + 20 = 100) e Cleo (200) sobrevivem. O único pedido pago da Ada foi de 50, que não atinge o limite.
Múltiplas condições e agrupamento por várias colunas
O HAVING aceita os mesmos operadores booleanos do WHERE — AND, OR, NOT — e você pode agrupar por mais de uma coluna para criar subgrupos:
Cada par (region, quarter) forma um grupo separado. A cláusula HAVING exige que o total seja maior que 100 e que existam pelo menos dois negócios. Só ('North', 'Q1') e ('South', 'Q2') passam nos dois critérios.
Caso prático: encontrando duplicatas
Usar GROUP BY ... HAVING COUNT(*) > 1 é a forma clássica de localizar valores duplicados em uma coluna:
Aparecem duas duplicatas. A partir daqui, normalmente você decide se vai mesclar contas, adicionar uma constraint UNIQUE ou limpar os dados — mas a query que faz essa descoberta tem sempre o mesmo formato.
HAVING sem GROUP BY
É raro, mas é permitido. Sem o GROUP BY, todo o resultado é tratado como um único grupo, e o HAVING filtra esse grupo inteiro de uma vez — ou você recebe todos os valores agregados, ou nada:
A linha aparece no resultado porque a soma dá 160. Se você mudar o limite para > 200, a consulta não retorna nenhuma linha. Na prática, você quase sempre vai usar HAVING junto com GROUP BY — mas é bom saber que a linguagem não exige isso.
Resumo rápido
GROUP BYagrupa as linhas em "baldes" por chave; as funções agregadas rodam dentro de cada balde.- Toda coluna do
SELECTque não é agregada precisa aparecer noGROUP BY. WHEREfiltra linhas antes do agrupamento;HAVINGfiltra grupos depois.- Funções agregadas como
COUNT(*)eSUM(...)vão noHAVING, nunca noWHERE. - O
HAVINGaceita múltiplas condições e pode referenciar apelidos definidos noSELECT.
Próximo passo: chaves estrangeiras
Agregar dados de uma única tabela já ajuda bastante, mas a maioria dos esquemas reais espalha as informações por várias tabelas — pedidos aqui, clientes ali, produtos em outro lugar. As chaves estrangeiras são o que conecta essas tabelas e mantém os relacionamentos consistentes. Esse é o assunto do próximo capítulo.
Perguntas frequentes
Qual a diferença entre WHERE e HAVING no SQLite?
O WHERE filtra linhas individuais antes do agrupamento. Já o HAVING filtra os grupos depois que a agregação acontece. Ou seja, WHERE amount > 100 mantém só as linhas com valor acima de 100, enquanto HAVING SUM(amount) > 100 mantém apenas os grupos cuja soma passa de 100. Funções agregadas como COUNT ou SUM não podem aparecer no WHERE — é justamente para isso que existe o HAVING.
Dá para usar HAVING sem GROUP BY no SQLite?
Dá sim. Sem o GROUP BY, o SQLite trata o resultado inteiro como um único grupo, e o HAVING filtra esse grupo como um todo. A consulta retorna uma linha ou nenhuma. É raro de ver na prática — geralmente, se você tem um HAVING, vai ter um GROUP BY junto.
Como filtrar grupos por COUNT no SQLite?
Coloque a função agregada no HAVING, nunca no WHERE. Por exemplo: SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 retorna os clientes com mais de um pedido. No SQLite, você ainda pode referenciar um alias da lista do SELECT dentro do HAVING.