Capa do Ebook gratuito SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

Novo curso

26 páginas

Agregações e métricas essenciais com COUNT, SUM e AVG

Capítulo 6

Tempo estimado de leitura: 0 minutos

+ Exercício

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 onde coluna nã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...
Download App

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 AS para 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 NULLIF em métricas derivadas.
  • Inclua contagens junto de médias para contextualizar (uma média com poucas observações pode ser instável).

Agora responda o exercício sobre o conteúdo:

Ao calcular a quantidade de clientes que compraram em uma tabela orders, qual abordagem garante a contagem de clientes únicos (e não o número de pedidos)?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

COUNT(DISTINCT customer_id) conta entidades únicas (valores distintos não nulos). COUNT(*) conta linhas (pedidos), e SUM soma valores numéricos, não sendo adequado para contar clientes únicos.

Próximo capitúlo

GROUP BY e HAVING para segmentação e critérios de corte em relatórios

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.