Saber fazer SELECT * FROM tabela é o básico do básico. O que separa quem está começando de quem realmente entrega valor com dados é a capacidade de responder perguntas de negócio: "qual o ticket médio?", "quais clientes compraram mais?". Para isso, você precisa de agregações, agrupamentos e subconsultas.
Funções de agregação: resumindo muitas linhas em um número
As funções de agregação pegam várias linhas e devolvem um único valor. As cinco fundamentais são:
- COUNT — conta linhas.
COUNT(*)conta tudo;COUNT(coluna)ignora nulos. - SUM — soma valores numéricos.
- AVG — calcula a média.
- MIN / MAX — o menor e o maior valor.
Sozinhas, elas resumem a tabela inteira. SELECT AVG(valor) FROM pedidos devolve o ticket médio de todos os pedidos.
GROUP BY: agregar por categoria
Quase nunca queremos o resumo de tudo — queremos por cliente, por mês, por categoria. O GROUP BY divide as linhas em grupos e aplica a agregação a cada um separadamente.
SELECT cliente_id, COUNT(*) AS total_pedidos, SUM(valor) AS gasto
FROM pedidos
GROUP BY cliente_id;
A regra que mais gera erro: toda coluna no SELECT que não está dentro de uma função de agregação precisa estar no GROUP BY.
WHERE vs HAVING: a diferença que sempre cai
Ambos filtram, mas em momentos diferentes do processamento. Pense numa linha de produção:
| Aspecto | WHERE | HAVING |
|---|---|---|
| Quando age | Antes de agrupar | Depois de agrupar |
| Filtra | Linhas individuais | Grupos (resultados agregados) |
| Pode usar SUM/COUNT? | Não | Sim |
Por exemplo: "clientes que gastaram mais de R$ 1000". O filtro é sobre a soma, então usa-se HAVING:
SELECT cliente_id, SUM(valor) AS gasto
FROM pedidos
WHERE valor > 0
GROUP BY cliente_id
HAVING SUM(valor) > 1000;
Subqueries: consultas dentro de consultas
Uma subquery é um SELECT aninhado dentro de outro. Útil quando o filtro depende de um cálculo. "Pedidos acima da média":
SELECT id, valor
FROM pedidos
WHERE valor > (SELECT AVG(valor) FROM pedidos);
A subquery interna roda primeiro, calcula a média, e o resultado vira o critério da consulta externa.
CTE: organizando com WITH
Quando a lógica fica complexa, subqueries aninhadas viram um nó. A CTE (Common Table Expression), com a cláusula WITH, dá um nome a um resultado intermediário, deixando a consulta legível como uma receita passo a passo.
WITH gasto_por_cliente AS (
SELECT cliente_id, SUM(valor) AS gasto
FROM pedidos
GROUP BY cliente_id
)
SELECT * FROM gasto_por_cliente
WHERE gasto > 1000;
A maioria das perguntas de negócio é uma agregação com um filtro. Quem entende a ordem em que o SQL processa — filtra, agrupa, filtra de novo — escreve consultas corretas de primeira em vez de chutar.
É comum em entrevista pedir para traduzir uma pergunta em palavras ("os 5 produtos mais vendidos do mês") em SQL, e o ponto avaliado quase sempre é justamente o GROUP BY com HAVING ou uma subquery bem colocada. No dia a dia isso aparece em todo relatório e dashboard que você precisa alimentar.
Conclusão
Agregações, GROUP BY, a distinção WHERE/HAVING, subqueries e CTEs formam o ferramental que transforma tabelas brutas em respostas. Tudo isso assume que você entende como as tabelas se relacionam — se ainda tem dúvidas aí, vale revisar modelagem relacional antes de seguir.
Knowing how to write SELECT * FROM table is the very basics. What separates people who are just starting out from those who really deliver value with data is the ability to answer business questions: "what's the average ticket?", "which customers bought the most?". For that, you need aggregations, grouping and subqueries.
Aggregate functions: boiling many rows down to one number
Aggregate functions take many rows and return a single value. The five fundamental ones are:
- COUNT — counts rows.
COUNT(*)counts everything;COUNT(column)ignores nulls. - SUM — adds up numeric values.
- AVG — computes the average.
- MIN / MAX — the smallest and largest value.
On their own, they summarize the entire table. SELECT AVG(valor) FROM pedidos returns the average ticket across all orders.
GROUP BY: aggregating by category
We almost never want a summary of everything — we want it per customer, per month, per category. GROUP BY splits the rows into groups and applies the aggregation to each one separately.
SELECT cliente_id, COUNT(*) AS total_pedidos, SUM(valor) AS gasto
FROM pedidos
GROUP BY cliente_id;
The rule that causes the most errors: every column in the SELECT that is not inside an aggregate function must be in the GROUP BY.
WHERE vs HAVING: the difference that always comes up
Both filter, but at different moments of processing. Think of a production line:
| Aspect | WHERE | HAVING |
|---|---|---|
| When it acts | Before grouping | After grouping |
| Filters | Individual rows | Groups (aggregated results) |
| Can use SUM/COUNT? | No | Yes |
For example: "customers who spent more than $1,000". The filter is on the sum, so you use HAVING:
SELECT cliente_id, SUM(valor) AS gasto
FROM pedidos
WHERE valor > 0
GROUP BY cliente_id
HAVING SUM(valor) > 1000;
Subqueries: queries inside queries
A subquery is a SELECT nested inside another one. Useful when the filter depends on a calculation. "Orders above the average":
SELECT id, valor
FROM pedidos
WHERE valor > (SELECT AVG(valor) FROM pedidos);
The inner subquery runs first, computes the average, and the result becomes the criterion of the outer query.
CTE: getting organized with WITH
When the logic gets complex, nested subqueries turn into a tangle. The CTE (Common Table Expression), with the WITH clause, gives a name to an intermediate result, making the query read like a step-by-step recipe.
WITH gasto_por_cliente AS (
SELECT cliente_id, SUM(valor) AS gasto
FROM pedidos
GROUP BY cliente_id
)
SELECT * FROM gasto_por_cliente
WHERE gasto > 1000;
Most business questions are an aggregation with a filter. Whoever understands the order in which SQL processes things — filter, group, filter again — writes correct queries on the first try instead of guessing.
In interviews it's common to be asked to translate a question in plain words ("the top 5 best-selling products of the month") into SQL, and what's being evaluated is almost always the GROUP BY with HAVING or a well-placed subquery. Day to day, this shows up in every report and dashboard you need to feed.
Conclusion
Aggregations, GROUP BY, the WHERE/HAVING distinction, subqueries and CTEs make up the toolkit that turns raw tables into answers. All of this assumes you understand how tables relate to each other — if you still have doubts there, it's worth reviewing relational modeling before moving on.