Agregações são operações que resumem várias linhas em um ou mais valores numéricos. Em análise de dados, elas servem para responder perguntas como “quantos pedidos tivemos?”, “qual foi o faturamento?”, “qual é o ticket médio?” e “como isso varia por mês, por canal ou por categoria?”. As funções mais usadas no dia a dia são COUNT, SUM e AVG. O ponto central é entender que, ao agregar, você troca detalhe por síntese: em vez de olhar linha a linha, você calcula métricas sobre um conjunto de linhas.
Uma agregação pode ser feita sobre o conjunto inteiro (uma única linha de resultado) ou por grupos (uma linha por grupo). Quando você usa GROUP BY, cada combinação de valores nas colunas agrupadas vira um “balde” (grupo), e as funções agregadas calculam métricas dentro de cada balde.
O que COUNT, SUM e AVG realmente medem
COUNT: contar linhas, valores e entidades
COUNT é usado para contagem, mas existem variações importantes:
COUNT(*)conta linhas, incluindo linhas onde colunas específicas podem estar nulas.COUNT(coluna)conta apenas linhas ondecolunanão éNULL.COUNT(DISTINCT coluna)conta valores distintos não nulos (ótimo para “quantos clientes únicos?”).
Isso significa que “quantidade de pedidos” pode ser COUNT(*) em uma tabela de pedidos, mas “quantidade de clientes que compraram” costuma ser COUNT(DISTINCT customer_id) na mesma tabela. Já “quantidade de pedidos com data de entrega preenchida” pode ser COUNT(delivered_at).
SUM: soma de valores (com atenção a nulos e duplicidades)
SUM soma valores numéricos. Em geral, SUM ignora NULL (ou seja, soma apenas valores existentes). Se você precisa tratar NULL como zero, use COALESCE(coluna, 0). Em métricas financeiras, é comum somar revenue, subtotal, tax, shipping etc.
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
Um cuidado prático: somas podem ser infladas se a granularidade da tabela não for a esperada (por exemplo, somar valores de pedidos em uma tabela de itens do pedido sem antes consolidar). A regra de ouro é: some na granularidade correta (pedido, item, cliente, dia) e só depois compare ou agrupe.
AVG: média aritmética e o que ela esconde
AVG calcula a média aritmética: soma dos valores dividida pela quantidade de valores não nulos. Assim como SUM, AVG ignora NULL. A média é útil para “ticket médio”, “tempo médio de entrega”, “desconto médio”, mas pode ser sensível a outliers. Em análises do dia a dia, é comum complementar a média com contagens (para saber se a amostra é grande) e, quando possível, com medidas robustas (como mediana), mas aqui vamos focar em AVG.
Base de exemplo (fictícia) para os exercícios
Para tornar os exemplos concretos, imagine uma tabela orders com uma linha por pedido:
orders(order_id, customer_id, order_date, status, channel, total_amount, discount_amount, delivered_at)E uma tabela order_items com uma linha por item:
order_items(order_id, product_id, quantity, unit_price)Os exemplos abaixo mostram padrões que você pode adaptar aos seus dados, mantendo o foco em agregações e métricas.
Agregações sem GROUP BY: métricas gerais
1) Quantidade total de pedidos
Quando você quer uma métrica global, sem segmentar por nada, use agregação sem GROUP BY:
SELECT COUNT(*) AS total_orders FROM orders;Esse resultado terá uma única linha com o total de pedidos.
2) Faturamento total e desconto total
SELECT SUM(total_amount) AS gross_revenue, SUM(discount_amount) AS total_discounts FROM orders;Se discount_amount puder ser nulo, e você quiser considerar nulo como zero, faça:
SELECT SUM(total_amount) AS gross_revenue, SUM(COALESCE(discount_amount, 0)) AS total_discounts FROM orders;3) Ticket médio (média do total do pedido)
SELECT AVG(total_amount) AS avg_order_value FROM orders;Para tornar a métrica mais interpretável, muitas equipes arredondam:
SELECT ROUND(AVG(total_amount), 2) AS avg_order_value FROM orders;Se você quiser calcular ticket médio apenas de pedidos concluídos (por exemplo, status = 'delivered'), aplique o filtro antes de agregar.
Agregações com GROUP BY: métricas por segmento
4) Pedidos e faturamento por canal
Agora você quer uma linha por canal:
SELECT channel, COUNT(*) AS total_orders, SUM(total_amount) AS revenue, ROUND(AVG(total_amount), 2) AS avg_ticket FROM orders GROUP BY channel;Interpretação prática: cada canal vira um grupo; dentro de cada grupo, você conta pedidos, soma faturamento e calcula ticket médio.
5) Pedidos por mês (e por status)
Uma análise comum é acompanhar volume ao longo do tempo. Dependendo do banco, você pode usar funções diferentes para truncar a data. Um padrão frequente é agrupar por mês usando uma expressão de data. Exemplo genérico:
SELECT DATE_TRUNC('month', order_date) AS order_month, COUNT(*) AS total_orders, SUM(total_amount) AS revenue FROM orders GROUP BY DATE_TRUNC('month', order_date) ORDER BY order_month;Se quiser quebrar por status também:
SELECT DATE_TRUNC('month', order_date) AS order_month, status, COUNT(*) AS total_orders, SUM(total_amount) AS revenue FROM orders GROUP BY DATE_TRUNC('month', order_date), status ORDER BY order_month, status;Repare que tudo que aparece no SELECT e não é agregado precisa estar no GROUP BY (ou ser derivado de algo que esteja no GROUP BY, dependendo do dialeto).
Passo a passo prático: construindo um relatório de métricas essenciais
Um jeito eficiente de trabalhar é montar o relatório em camadas: primeiro valide a base (granularidade e filtros), depois calcule as métricas, depois organize a saída.
Passo 1: defina a população (quais linhas entram)
Exemplo: você quer métricas de pedidos pagos (ou entregues) no último trimestre. Você aplica o filtro antes de agregar. Exemplo:
SELECT COUNT(*) AS total_orders, SUM(total_amount) AS revenue, ROUND(AVG(total_amount), 2) AS avg_ticket FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-10-01' AND order_date < DATE '2026-01-01';O filtro define a população. Se você errar aqui, todas as métricas ficam “corretas” matematicamente, mas erradas para o negócio.
Passo 2: escolha as métricas essenciais (e nomeie bem)
Uma seleção típica para um painel simples:
- Volume:
COUNT(*)(pedidos) - Receita:
SUM(total_amount) - Ticket médio:
AVG(total_amount) - Clientes únicos:
COUNT(DISTINCT customer_id)
Exemplo:
SELECT COUNT(*) AS total_orders, COUNT(DISTINCT customer_id) AS unique_customers, SUM(total_amount) AS revenue, ROUND(AVG(total_amount), 2) AS avg_ticket FROM orders WHERE status IN ('paid', 'delivered');Note que COUNT(DISTINCT) é mais caro em performance em bases grandes, mas é a forma correta quando você precisa de entidades únicas.
Passo 3: segmente com GROUP BY (quando fizer sentido)
Agora você quer as mesmas métricas por canal:
SELECT channel, COUNT(*) AS total_orders, COUNT(DISTINCT customer_id) AS unique_customers, SUM(total_amount) AS revenue, ROUND(AVG(total_amount), 2) AS avg_ticket FROM orders WHERE status IN ('paid', 'delivered') GROUP BY channel ORDER BY revenue DESC;Ordenar por revenue ajuda a priorizar os canais mais relevantes.
Armadilhas comuns e como evitá-las
COUNT(*) vs COUNT(coluna): diferença que muda o resultado
Se você quer contar pedidos entregues e usa COUNT(delivered_at), você está contando apenas os pedidos com data de entrega preenchida. Isso pode ser exatamente o que você quer, mas não é igual a COUNT(*) com filtro por status. Compare:
SELECT COUNT(*) AS delivered_orders FROM orders WHERE status = 'delivered';SELECT COUNT(delivered_at) AS delivered_orders FROM orders;O segundo depende de delivered_at estar preenchido corretamente. Se houver pedidos com status entregue mas sem data (ou vice-versa), as métricas divergem. Em relatórios, prefira a regra de negócio mais confiável (status ou data), e valide consistência.
AVG de valores já agregados: média de médias pode enganar
Um erro comum é calcular ticket médio por dia e depois tirar a média desses tickets diários para obter o ticket médio do mês. Isso dá o mesmo resultado apenas se todos os dias tiverem o mesmo número de pedidos. O correto para o mês é:
SELECT ROUND(SUM(total_amount) / COUNT(*), 2) AS avg_ticket_month FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-12-01' AND order_date < DATE '2026-01-01';Esse padrão (soma dividido por contagem) é equivalente a AVG(total_amount) quando não há nulos em total_amount, mas deixa explícito que você quer uma média ponderada pelo número de pedidos.
SUM em tabela de itens vs tabela de pedidos
Se você tem order_items, pode calcular receita como SUM(quantity * unit_price). Isso é útil quando o total do pedido não existe ou quando você quer recalcular com regras específicas. Exemplo:
SELECT SUM(quantity * unit_price) AS items_revenue FROM order_items;Mas se você misturar isso com uma tabela de pedidos que já tem total_amount, pode acabar somando duas vezes ou comparando métricas com definições diferentes (bruto vs líquido, com frete vs sem frete). Defina claramente a fonte da verdade para cada métrica.
Métricas essenciais derivadas com COUNT, SUM e AVG
Além das métricas “puras”, você frequentemente cria indicadores derivados combinando agregações.
Taxa de pedidos com desconto
Você pode calcular a proporção de pedidos que tiveram desconto. Um padrão é somar 1 para pedidos com desconto e dividir pelo total. Exemplo:
SELECT COUNT(*) AS total_orders, SUM(CASE WHEN COALESCE(discount_amount, 0) > 0 THEN 1 ELSE 0 END) AS discounted_orders, ROUND( 1.0 * SUM(CASE WHEN COALESCE(discount_amount, 0) > 0 THEN 1 ELSE 0 END) / COUNT(*), 4 ) AS discount_rate FROM orders WHERE status IN ('paid', 'delivered');O 1.0 * força divisão em ponto flutuante em muitos bancos, evitando truncamento inteiro.
ARPU simples (receita por cliente único)
Uma métrica comum em produtos e assinaturas é receita por usuário (ou cliente) no período:
SELECT SUM(total_amount) AS revenue, COUNT(DISTINCT customer_id) AS unique_customers, ROUND( SUM(total_amount) / NULLIF(COUNT(DISTINCT customer_id), 0), 2 ) AS revenue_per_customer FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-12-01' AND order_date < DATE '2026-01-01';NULLIF(..., 0) evita divisão por zero quando não há clientes no período.
Ticket médio por cliente (média do gasto total por cliente)
“Ticket médio” pode significar duas coisas diferentes:
- Por pedido:
AVG(total_amount) - Por cliente: média do total gasto por cliente no período
Para “por cliente”, você precisa primeiro consolidar por cliente e depois tirar a média desses totais. Exemplo:
WITH customer_totals AS ( SELECT customer_id, SUM(total_amount) AS total_spent FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-12-01' AND order_date < DATE '2026-01-01' GROUP BY customer_id ) SELECT ROUND(AVG(total_spent), 2) AS avg_spent_per_customer FROM customer_totals;Esse padrão (agregar em um nível, depois agregar de novo) é muito útil para evitar interpretações erradas.
Checagens rápidas de qualidade para métricas agregadas
Compare contagens: pedidos vs clientes únicos
Uma checagem simples é garantir que unique_customers não seja maior que total_orders quando cada pedido tem um cliente. Exemplo:
SELECT COUNT(*) AS total_orders, COUNT(DISTINCT customer_id) AS unique_customers FROM orders WHERE status IN ('paid', 'delivered');Se unique_customers aparecer maior, isso indica problema de dados (por exemplo, customer_id variando por linha de pedido de forma inesperada) ou erro na consulta.
Valide nulos em colunas-chave para métricas
Se total_amount tiver muitos nulos, AVG(total_amount) pode ser calculado sobre uma amostra menor do que você imagina. Uma forma de enxergar isso é comparar COUNT(*) com COUNT(total_amount):
SELECT COUNT(*) AS total_rows, COUNT(total_amount) AS rows_with_amount, COUNT(*) - COUNT(total_amount) AS rows_missing_amount FROM orders;Isso ajuda a decidir se você deve filtrar, imputar com COALESCE ou corrigir a origem do dado.
Exercício guiado: relatório mensal por canal com métricas essenciais
Objetivo: gerar uma tabela com uma linha por mês e canal, contendo pedidos, clientes únicos, receita e ticket médio, apenas para pedidos pagos/entregues.
Passo 1: defina o período e o status
WITH base AS ( SELECT order_date, channel, customer_id, total_amount FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-01-01' AND order_date < DATE '2026-01-01' )A CTE base deixa explícito o recorte e reduz ruído.
Passo 2: agregue por mês e canal
WITH base AS ( SELECT order_date, channel, customer_id, total_amount FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-01-01' AND order_date < DATE '2026-01-01' ) SELECT DATE_TRUNC('month', order_date) AS order_month, channel, COUNT(*) AS total_orders, COUNT(DISTINCT customer_id) AS unique_customers, SUM(total_amount) AS revenue, ROUND(AVG(total_amount), 2) AS avg_ticket FROM base GROUP BY DATE_TRUNC('month', order_date), channel ORDER BY order_month, revenue DESC;Esse formato já é adequado para exportar para um relatório, alimentar um gráfico ou servir de base para um dashboard.
Passo 3: adicione uma métrica derivada (receita por cliente)
Você pode enriquecer o relatório com uma métrica derivada usando as agregações já calculadas:
WITH base AS ( SELECT order_date, channel, customer_id, total_amount FROM orders WHERE status IN ('paid', 'delivered') AND order_date >= DATE '2025-01-01' AND order_date < DATE '2026-01-01' ), agg AS ( SELECT DATE_TRUNC('month', order_date) AS order_month, channel, COUNT(*) AS total_orders, COUNT(DISTINCT customer_id) AS unique_customers, SUM(total_amount) AS revenue, AVG(total_amount) AS avg_ticket FROM base GROUP BY DATE_TRUNC('month', order_date), channel ) SELECT order_month, channel, total_orders, unique_customers, revenue, ROUND(avg_ticket, 2) AS avg_ticket, ROUND(revenue / NULLIF(unique_customers, 0), 2) AS revenue_per_customer FROM agg ORDER BY order_month, revenue DESC;Separar em agg facilita reutilizar as métricas e evita repetir expressões longas.
Boas práticas de escrita de métricas com agregações
- Nomeie colunas calculadas com
ASpara deixar o resultado autoexplicativo (ex.:avg_ticket,unique_customers). - Garanta a granularidade correta antes de somar ou tirar média. Se a tabela tem uma linha por item, suas métricas serão por item, não por pedido.
- Use
COUNT(DISTINCT)quando a pergunta for sobre entidades únicas (clientes, produtos, pedidos únicos), e não apenas número de linhas. - Trate divisão por zero com
NULLIFem métricas derivadas. - Inclua contagens junto de médias para contextualizar (uma média com poucas observações pode ser instável).