Módulo 5: Agregação e GROUP BY
COUNT, SUM, AVG, GROUP BY, HAVING e o FILTER (WHERE …), jeito Postgres
Antes de começar
GROUP BY e HAVING funcionam igual à Trilha 1. A novidade que
vale ouro é o FILTER (WHERE …): contar/somar só as linhas que batem
uma condição, por coluna, sem aquele SUM(CASE WHEN … THEN 1 ELSE 0 END).
COUNT(*) FILTER (WHERE resultado = 'passou'): quantas passaram.- Dá pra ter várias na mesma query, cada uma com seu filtro, lado a lado.
- É mais curto e mais legível que
SUM(CASE …), e faz a mesma coisa. bool_or(cond)/bool_and(cond): "existe alguma linha no grupo onde…" / "todas as linhas do grupo…".
Cuidado de tipo (Módulo 2): AVG de inteiros ou
uma divisão inteira zeram casas decimais, então force ::numeric e
ROUND(x, 2).
Olhar de QA: quase toda métrica de qualidade
(taxa de aprovação, backlog por severidade, faturamento por categoria) é um
GROUP BY com FILTER.
Dois exemplos
Teoria
COUNT(*),COUNT(coluna)(ignora NULL),SUM,AVG,MIN,MAX.GROUP BY col: uma linha por valor decol. Tudo que não é agregado tem que estar no GROUP BY.HAVING: filtra depois de agrupar (é o WHERE dos agregados).agg FILTER (WHERE cond): agrega só as linhas que passam emcond.bool_or(cond)/bool_and(cond): "algum" / "todos" dentro do grupo.ROUND(AVG(x)::numeric, 2),x * 100.0 / y: pra não perder casas.
Exemplos de aplicação
Um exemplo montado pra cada item da teoria acima. Não precisa rodar, é pra ver a cara do recurso e entender cada pedaço. Os valores são ilustrativos.
Agregações + COUNT(coluna)
SELECT
COUNT(*) AS execucoes,
COUNT(defeito_id) AS com_defeito,
ROUND(AVG(duracao_seg)::numeric, 1) AS duracao_media,
MAX(duracao_seg) AS mais_lenta
FROM execucoes_teste;
COUNT(*) conta linhas; COUNT(defeito_id) ignora as em que a coluna é NULL. AVG/MAX resumem o resto. O ::numeric antes do ROUND é pra média sair com casas decimais.
GROUP BY
SELECT suite,
resultado,
COUNT(*) AS qtd
FROM execucoes_teste
GROUP BY suite, resultado
ORDER BY suite, resultado;
Agrupa por combinação de suite + resultado: uma linha por par. Regra do Postgres: toda coluna do SELECT que não está numa função de agregação precisa estar no GROUP BY.
HAVING
SELECT modulo,
COUNT(*) AS qtd_defeitos
FROM defeitos
GROUP BY modulo
HAVING COUNT(*) > 10;
HAVING é o filtro que roda depois de agrupar; só mantém os módulos com mais de 10 defeitos. Não dá pra usar WHERE COUNT(*) > 10: o WHERE acontece antes da contagem existir.
FILTER (WHERE ...)
SELECT
suite,
COUNT(*) AS total,
COUNT(*) FILTER (WHERE resultado = 'passou') AS passaram,
COUNT(*) FILTER (WHERE resultado = 'falhou') AS falharam
FROM execucoes_teste
GROUP BY suite;
FILTER aplica uma condição só àquela agregação. É a versão limpa do SUM(CASE WHEN ... THEN 1 ELSE 0 END) do SQLite: mesma ideia (contar um subconjunto do grupo), sintaxe mais direta.
bool_or / bool_and
SELECT
pedido_id,
bool_or(quantidade <= 0) AS tem_item_invalido,
bool_and(quantidade > 0) AS todos_itens_ok
FROM itens_pedido
GROUP BY pedido_id;
bool_or(cond) é true se algum item do grupo satisfaz a condição; bool_and(cond) só se todos satisfazem. Ótimo pra marcar "este pedido tem pelo menos um item furado".
Porcentagem sem perder casas
SELECT
suite,
ROUND(
100.0 * COUNT(*) FILTER (WHERE resultado = 'passou') / COUNT(*),
1
) AS aprovacao_pct
FROM execucoes_teste
GROUP BY suite;
Multiplicar por 100.0 (com o .0) força a conta a ser decimal; senão a divisão de inteiros truncaria pra 0. ROUND(x, 1) deixa uma casa.
Pegadinhas deste módulo
Os erros que quase todo mundo comete aqui. Ler agora poupa meia hora de confusão depois, e alguns deles são a causa de relatório errado em projeto real.
NOT INcomNULLna subconsulta devolve zero linhas. Se a subconsulta deWHERE id NOT IN (SELECT cliente_id FROM pedidos)contiver um únicoNULL, o resultado é vazio, sempre, sem erro. PrefiraNOT EXISTS, que não tem esse problema, ou acrescenteWHERE cliente_id IS NOT NULLna subconsulta.- Subconsulta escalar precisa devolver exatamente uma linha.
= (SELECT ...)que devolve duas linhas dá erro; que devolve nenhuma viraNULLe a comparação falha em silêncio. - CTE não é otimização, é legibilidade.
WITHdeixa a query compreensível, mas em vários bancos não torna nada mais rápido. Use pela clareza, que já é motivo suficiente. EXISTScostuma ser melhor queINem lista grande.EXISTSpara assim que acha a primeira linha;INpode materializar a lista inteira.- Subconsulta correlacionada roda uma vez por linha. Funciona bem em 60 clientes e trava em 6 milhões. Em banco de produção, desconfie, e teste com volume antes de rodar.
Prática
Escreva a query e clique Rodar (ou Ctrl+Enter).
carregando banco…