Aula 4.4: Agregação condicional
A agregação condicional em SQL permite calcular várias métricas em uma única consulta, sem executar várias consultas separadas. A ideia é simples: dentro de uma função de agregação (SUM, COUNT, AVG), usa-se uma expressão condicional (na maioria das vezes CASE, mas em alguns SGBDs pode ser outro operador condicional) que inclui no cálculo apenas as linhas que atendem à condição.
Essa abordagem é especialmente útil para relatórios, dashboards e análises, quando você precisa obter vários indicadores ao mesmo tempo: contagens, somas, proporções, divisões por status etc.
Nesta aula, vamos ver:
- como a agregação condicional funciona;
- como calcular contagens, somas e médias condicionais;
- como construir relatórios no estilo pivot (transformar linhas em colunas) com
CASE.
Ideia básica
Modelo clássico de agregação condicional:
AGGREGATION_FUNCTION(CASE WHEN condition THEN value ELSE 0 END)
ou versão curta:
AGGREGATION_FUNCTION(CASE WHEN condition THEN 1 END)
O que acontece:
CASEretorna um valor de acordo com a condição. Na versão curta, se a condição não for atendida, ele retornaNULL;- a função de agregação acumula o resultado por grupo;
- na saída, você obtém uma métrica baseada na condição.
Soma condicional
Exemplo: somas de vendas por funcionário com divisão por faixas de valor
SELECT
staff_id,
SUM(CASE WHEN amount < 2 THEN amount ELSE 0 END) AS low_amount_total,
SUM(CASE WHEN amount BETWEEN 2 AND 6 THEN amount ELSE 0 END) AS medium_amount_total,
SUM(CASE WHEN amount > 6 THEN amount ELSE 0 END) AS high_amount_total
FROM payment
GROUP BY staff_id;
Resultado: uma consulta retorna três somas diferentes para cada funcionário.
Média condicional
Exemplo: valor médio de pagamentos altos por funcionário
SELECT
staff_id,
AVG(CASE WHEN amount >= 5 THEN amount END) AS avg_big_payment
FROM payment
GROUP BY staff_id;
Resultado: para cada funcionário, calcula-se o valor médio apenas dos pagamentos em que amount >= 5.
Por que ELSE 0 normalmente não é necessário aqui:
AVGé calculado como a soma dos valores dividida pela quantidade deles;- se colocar
0para linhas que não atendem à condição, esses zeros entram no cálculo e reduzem a média; - por isso, em
AVGcondicional, normalmente usa-seELSE NULLou não se informaELSE.
Contagem condicional
Exemplo: quantos pagamentos em cada faixa de valor
SELECT
customer_id,
COUNT(CASE WHEN amount < 2 THEN 1 END) AS low_payments,
COUNT(CASE WHEN amount BETWEEN 2 AND 6 THEN 1 END) AS medium_payments,
COUNT(CASE WHEN amount > 6 THEN 1 END) AS high_payments
FROM payment
GROUP BY customer_id;
Resultado: para cada cliente, a consulta retorna o número de pagamentos “baixos”, “médios” e “altos”.
Por que ELSE não é necessário aqui:
- se a condição for verdadeira,
CASEretorna1; - se a condição for falsa e
ELSEnão for informado,CASEretornaNULL; COUNT(expression)conta apenas valores não-NULL, então só entram as linhas em que a condição foi atendida.
Importante: não use ELSE 0 nesse padrão com COUNT, porque 0 também não é NULL, e então COUNT passa a contar quase todas as linhas.
Exemplo: contagem de aluguéis devolvidos e não devolvidos
SELECT
staff_id,
COUNT(return_date) AS returned_count,
COUNT(CASE WHEN return_date IS NULL THEN 1 END) AS not_returned_count
FROM rental
GROUP BY staff_id;
O que acontece aqui:
COUNT(return_date)conta apenas valores não-NULL, ou seja, quantidade de aluguéis devolvidos;COUNT(CASE WHEN return_date IS NULL THEN 1 END)conta apenas linhas em que a data de devolução está ausente, ou seja, aluguéis não devolvidos;GROUP BY staff_idforma contadores separados para cada funcionário.
Resultado: em uma única consulta, você obtém as duas métricas para cada funcionário.
Técnica de pivot com CASE
O que é pivot em SQL
Pivot (rotação) é a transformação de linhas em colunas. Normalmente, os dados de origem têm categorias em linhas, mas no relatório você precisa ver essas categorias como colunas separadas.
Em muitos SGBDs existe um operador especial PIVOT, mas a forma universal e portátil é a agregação condicional com CASE.
Modelo básico de pivot
SELECT
group_column,
SUM(CASE WHEN pivot_key = 'A' THEN measure ELSE 0 END) AS col_a,
SUM(CASE WHEN pivot_key = 'B' THEN measure ELSE 0 END) AS col_b,
SUM(CASE WHEN pivot_key = 'C' THEN measure ELSE 0 END) AS col_c
FROM source_table
GROUP BY group_column;
Exemplo: pivot por classificação de filmes
Abaixo, para cada categoria de filmes, contamos a quantidade de filmes por classificação em colunas separadas:
SELECT
c.name AS category,
COUNT(CASE WHEN f.rating = 'G' THEN 1 END) AS g_films_count,
AVG(CASE WHEN f.rating = 'G' THEN length ELSE 0 END) AS g_films_average_length,
COUNT(CASE WHEN f.rating = 'PG' THEN 1 END) AS pg_films_count,
AVG(CASE WHEN f.rating = 'PG' THEN length ELSE 0 END) AS pg_films_average_length,
COUNT(CASE WHEN f.rating = 'PG-13' THEN 1 END) AS pg13_films_count,
AVG(CASE WHEN f.rating = 'PG-13' THEN length ELSE 0 END) AS pg13_films_average_length,
COUNT(CASE WHEN f.rating = 'R' THEN 1 END) AS r_films_count,
AVG(CASE WHEN f.rating = 'R' THEN length ELSE 0 END) AS r_films_average_length,
COUNT(CASE WHEN f.rating = 'NC-17' THEN 1 END) AS nc17_films_rating,
AVG(CASE WHEN f.rating = 'NC-17' THEN length ELSE 0 END) AS nc17_films_average_length
FROM film f
JOIN film_category fc ON f.film_id = fc.film_id
JOIN category c ON fc.category_id = c.category_id
GROUP BY c.name
ORDER BY c.name;
Resultado: cada linha é uma categoria, e as colunas mostram a quantidade de filmes de cada classificação e sua duração média.
Recomendações práticas
- Para
SUM, geralmente usa-seELSE 0, para que linhas fora da condição deem contribuição zero. - Para
COUNT(CASE ...),ELSEnormalmente não é necessário:COUNTjá ignoraNULL. - Para
AVG(CASE ...), usa-se com mais frequênciaELSE NULLou versão semELSE, para não reduzir a média. - Se houver muitas métricas condicionais, dê aliases claros às colunas (
*_count,*_total). - Verifique se as condições em
CASEnão se sobrepõem, quando as categorias devem ser mutuamente exclusivas. - Para consultas grandes, primeiro valide a lógica em um conjunto pequeno de dados ou com
LIMIT.
Aplicação prática
Pivot por dias da semana:
SELECT MONTH(rental_date) AS rental_month, SUM(CASE WHEN DAYNAME(rental_date) = 'Monday' THEN 1 ELSE 0 END) AS monday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Tuesday' THEN 1 ELSE 0 END) AS tuesday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Wednesday' THEN 1 ELSE 0 END) AS wednesday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Thursday' THEN 1 ELSE 0 END) AS thursday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Friday' THEN 1 ELSE 0 END) AS friday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Saturday' THEN 1 ELSE 0 END) AS saturday_rentals, SUM(CASE WHEN DAYNAME(rental_date) = 'Sunday' THEN 1 ELSE 0 END) AS sunday_rentals FROM rental GROUP BY MONTH(rental_date);Essa consulta mostra quantos aluguéis foram feitos em cada mês por dia da semana.
Cálculo de proporções por condição:
SELECT customer_id, SUM(CASE WHEN amount >= 5 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS high_payment_share FROM payment GROUP BY customer_id;
Brevemente sobre a sintaxe FILTER
Em alguns SGBDs (por exemplo, PostgreSQL), a condição pode ser movida de CASE para FILTER:
COUNT(*) FILTER (WHERE condition)
SUM(amount) FILTER (WHERE condition)
O sentido aqui é o mesmo da agregação condicional com CASE: a função de agregação processa não todas as linhas, mas apenas as que passaram na condição do WHERE dentro de FILTER.
Essa sintaxe costuma ser mais fácil de ler, especialmente se em um mesmo SELECT você precisa calcular várias métricas diferentes com condições diferentes.
Por exemplo:
SELECT
customer_id,
COUNT(*) AS total_payments,
COUNT(*) FILTER (WHERE amount >= 5) AS big_payments_count,
SUM(amount) FILTER (WHERE amount >= 5) AS big_payments_total
FROM payment
GROUP BY customer_id;
Nesse exemplo:
COUNT(*)conta todos os pagamentos do cliente;COUNT(*) FILTER (WHERE amount >= 5)conta apenas os pagamentos “grandes”;SUM(amount) FILTER (WHERE amount >= 5)soma apenas esses pagamentos.
Ou seja, FILTER faz o mesmo trabalho que CASE, mas em uma forma mais compacta. Ao mesmo tempo, é importante lembrar que essa sintaxe não é suportada por todos os SGBDs.
Principais conclusões desta aula
- Agregação condicional é uma função de agregação + expressão condicional, na maioria das vezes
CASE. - Com
SUM(CASE ...),COUNT(CASE ...)eAVG(CASE ...), você pode obter várias métricas em uma única consulta. - Pivot com
CASEé uma forma universal de transformar linhas em colunas. - Essa abordagem é bem adequada para relatórios analíticos e dashboards.
Ao dominar a agregação condicional, você conseguirá escrever consultas SQL mais compactas e expressivas para análise de negócios.