SQL para análise de dados · Guias · 30 de outubro de 2026 · 10 min de leitura

Funções de agregação no SQL: COUNT, SUM, AVG, MIN e MAX com exemplos

Muitos cartões de embarque pequenos afunilando num único número grande num painel de aeroporto

Uma função de agregação recebe muitas linhas e devolve um único valor. As cinco que você vai usar todo dia são COUNT (quantos), SUM (soma), AVG (média), MIN e MAX (menor e maior). Sem GROUP BY, elas resumem a tabela inteira em uma linha; com GROUP BY, devolvem uma linha por grupo. Duas regras explicam a maioria das surpresas: todas, exceto COUNT(*), ignoram NULL, e COUNT(*), COUNT(coluna) e COUNT(DISTINCT coluna) contam três coisas diferentes.

Os exemplos rodam no banco de dados de uma companhia aérea usado no curso de SQL da Nomto, com sintaxe do PostgreSQL executada no DuckDB (o motor da sala de SQL da Nomto). Os valores estão em dólares americanos, os textos dos dados em inglês, e cada tabela de resultado saiu da execução real da consulta.

As cinco funções de agregação numa consulta

A tabela ticket_flights tem uma linha por passageiro por trecho de voo, com a tarifa em amount:

SELECT COUNT(*)              AS segments,
       SUM(amount)           AS revenue,
       ROUND(AVG(amount), 2) AS avg_fare,
       MIN(amount)           AS min_fare,
       MAX(amount)           AS max_fare
FROM ticket_flights;
segments revenue avg_fare min_fare max_fare
1045726 207669809.00 198.59 30.00 2033.00
função devolve funciona com
COUNT(*) número de linhas qualquer tabela
COUNT(col) número de valores não NULL qualquer tipo
SUM(col) soma números
AVG(col) média aritmética números
MIN(col), MAX(col) menor, maior números, texto, datas

COUNT(*) vs COUNT(coluna) vs COUNT(DISTINCT coluna)

Esta é a diferença que mais importa na prática:

SELECT COUNT(*)                        AS flights,
       COUNT(actual_departure)         AS departed,
       COUNT(DISTINCT arrival_airport) AS destinations,
       COUNT(DISTINCT aircraft_code)   AS models_used
FROM flights;
flights departed destinations models_used
33121 16773 104 8

COUNT(1) dá o mesmo resultado que COUNT(*) e não é mais rápido em nenhum banco moderno. Escolha um e seja consistente.

O COUNT(DISTINCT ...) responde “quantos X únicos”. A tabela tickets tem uma linha por bilhete de passageiro, e vários bilhetes podem pertencer a uma mesma reserva:

SELECT COUNT(*)                      AS tickets,
       COUNT(contact_data->>'email') AS with_email,
       COUNT(DISTINCT book_ref)      AS bookings
FROM tickets;
tickets with_email bookings
366733 199083 262788

Para contar combinações distintas de duas colunas, PostgreSQL e DuckDB não aceitam COUNT(DISTINCT a, b) (o MySQL aceita). Conte as linhas de um SELECT DISTINCT:

SELECT COUNT(*) AS routes
FROM (SELECT DISTINCT departure_airport, arrival_airport FROM flights) r;
routes
618

SUM e AVG

O SUM soma, e o AVG divide essa soma pelo número de valores não NULL. Os dois só aceitam números. Por classe tarifária, ordenado pela tarifa média:

SELECT fare_conditions,
       COUNT(*)              AS segments,
       ROUND(SUM(amount))    AS revenue,
       ROUND(AVG(amount), 2) AS avg_fare,
       MIN(amount)           AS min_fare,
       MAX(amount)           AS max_fare
FROM ticket_flights
GROUP BY fare_conditions
ORDER BY avg_fare DESC;
fare_conditions segments revenue avg_fare min_fare max_fare
Business 107642 55051796 511.43 91.00 2033.00
Comfort 17291 5661169 327.41 199.00 476.00
Economy 920793 146956844 159.6 30.00 745.00

A armadilha da média das médias

Qual é a tarifa média? Tirar a média das três médias por classe dá uma resposta bem diferente de tirar a média de todos os trechos:

SELECT ROUND(AVG(avg_fare), 2) AS avg_of_averages,
       (SELECT ROUND(AVG(amount), 2) FROM ticket_flights) AS true_average
FROM (
  SELECT fare_conditions, AVG(amount) AS avg_fare
  FROM ticket_flights
  GROUP BY fare_conditions
) t;
avg_of_averages true_average
332.81 198.59

A média das médias dá aos 17.291 trechos da Comfort o mesmo peso que aos 920.793 da Economy. Calcule sempre a média sobre as linhas brutas ou faça SUM(amount) / SUM(segments) a partir dos totais agrupados. É o mesmo erro de tirar a média simples do ticket médio de cada loja numa rede de varejo: a loja pequena pesa tanto quanto a grande.

Divisão inteira

No PostgreSQL, no SQL Server e no SQLite, dividir um inteiro por outro descarta o resto: razões no estilo COUNT(*) / COUNT(*) viram 0 ou 1 sem aviso. No DuckDB, o / devolve uma fração (7 / 2 é 3,5; 7 // 2 é 3), e no MySQL o / também. Multiplicar primeiro por 100.0, como nos exemplos abaixo, dá a resposta certa em todo lugar.

MIN e MAX

MIN e MAX funcionam com qualquer tipo que possa ser ordenado: números, datas e texto.

SELECT MIN(book_date)    AS first_booking,
       MAX(book_date)    AS last_booking,
       MIN(total_amount) AS smallest,
       MAX(total_amount) AS largest
FROM bookings;
first_booking last_booking smallest largest
2026-06-17 14:05:00 2026-08-11 18:00:00 34.00 12045.00

Em texto, MIN(city) devolve o primeiro valor em ordem alfabética (Abakan) e MAX(city) o último (Yuzhno-Sakhalinsk). O MAX devolve o valor, não a linha: para ver qual reserva teve o maior valor, use ORDER BY total_amount DESC LIMIT 1 (ORDER BY no SQL), ou uma função de janela quando precisar disso por grupo (veja funções de janela).

Funções de agregação e NULL

O guia de NULL no SQL mostra cada um desses pontos em dados reais, incluindo quanto uma média se desloca quando valores desconhecidos viram zero.

Agregações com GROUP BY e HAVING

O GROUP BY divide as linhas em grupos, e cada agregação é calculada uma vez por grupo. O HAVING depois filtra os grupos por uma agregação:

SELECT departure_airport, COUNT(*) AS departures
FROM flights
GROUP BY departure_airport
HAVING COUNT(*) >= 1500
ORDER BY departures DESC;
departure_airport departures
DME 3217
SVO 2981
LED 1900
VKO 1719

Duas regras decorrem da ordem em que o SQL executa uma consulta:

Os detalhes, com os erros comuns, estão em GROUP BY e HAVING.

Agregação condicional: FILTER e CASE

Muitas vezes você quer várias contagens com condições diferentes numa única linha. A cláusula FILTER aplica uma condição a uma só agregação:

SELECT COUNT(*) FILTER (WHERE status = 'Cancelled') AS cancelled,
       COUNT(*) FILTER (WHERE status = 'Delayed')   AS delayed,
       SUM(CASE WHEN status = 'Cancelled' THEN 1 ELSE 0 END) AS cancelled_case,
       ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'Cancelled') / COUNT(*), 2) AS cancelled_pct
FROM flights;
cancelled delayed cancelled_case cancelled_pct
414 41 414 1.25

O FILTER funciona no PostgreSQL (9.4+), no DuckDB e no SQLite (3.30+). MySQL e SQL Server não têm; neles, use SUM(CASE WHEN ... THEN 1 ELSE 0 END) ou COUNT(CASE WHEN ... THEN 1 END), que devolvem o mesmo número, como mostra a coluna cancelled_case.

O mesmo padrão dá participações no total numa única passada:

SELECT ROUND(SUM(amount)) AS revenue,
       ROUND(SUM(amount) FILTER (WHERE fare_conditions = 'Business')) AS business_revenue,
       ROUND(100.0 * SUM(amount) FILTER (WHERE fare_conditions = 'Business') / SUM(amount), 1) AS business_pct
FROM ticket_flights;
revenue business_revenue business_pct
207669809 55051796 26.5

A classe Business é cerca de 10% dos trechos, mas 26,5% da receita. Mais lógica condicional no guia de CASE WHEN no SQL; colocar essas contagens lado a lado, uma coluna por valor, é exatamente o que se chama de pivot no SQL.

Totais errados depois de um JOIN

As agregações contam as linhas que recebem. Um join que liga uma linha a várias repete essa linha, e o SUM a soma várias vezes. Os totais das reservas somados depois de juntar com os bilhetes:

SELECT SUM(b.total_amount) AS wrong_revenue,
       (SELECT SUM(total_amount) FROM bookings) AS right_revenue
FROM bookings b
JOIN tickets t ON t.book_ref = b.book_ref;
wrong_revenue right_revenue
343206192.00 207669809.00

Uma reserva com três passageiros tem três bilhetes, então o total dela foi somado três vezes. A consulta roda sem erros e o número parece plausível. Agregue cada tabela no seu próprio nível primeiro e depois junte os resultados, ou use COUNT(DISTINCT ...) quando só precisar de contagens. O guia de JOIN no SQL explica essa multiplicação de linhas em detalhe.

Aninhar agregações

Você não pode colocar uma agregação diretamente dentro de outra: SELECT MAX(COUNT(*)) FROM flights GROUP BY departure_airport falha com “aggregate function calls cannot be nested”. Agregue numa subconsulta ou numa CTE e depois agregue o resultado:

SELECT AVG(n) AS avg_tickets_per_booking, MAX(n) AS max_tickets
FROM (SELECT book_ref, COUNT(*) AS n FROM tickets GROUP BY book_ref) t;
avg_tickets_per_booking max_tickets
1.3955469808362635 5

Em média, uma reserva tem 1,4 bilhete, e a maior tem cinco. Veja o guia de CTE no SQL para escrever agregações em várias etapas de forma legível.

Outras funções de agregação úteis

SELECT split_part(model, ' ', 1) AS maker,
       COUNT(*)   AS models,
       MAX(range) AS max_range,
       STRING_AGG(model, ', ' ORDER BY range DESC) AS line_up
FROM aircrafts
GROUP BY maker
ORDER BY max_range DESC;
maker models max_range line_up
Boeing 3 11100 Boeing 777-300, Boeing 767-300, Boeing 737-300
Airbus 3 6700 Airbus A319-100, Airbus A320-200, Airbus A321-200
Sukhoi 1 3000 Sukhoi Superjet-100
Bombardier 1 2700 Bombardier CRJ-200
Cessna 1 1200 Cessna 208 Caravan

Toda agregação também pode ser usada como função de janela com OVER (...), que mantém as linhas individuais em vez de resumi-las: é assim que se calculam totais acumulados e participações dentro de um grupo.

Se você vem do Excel, o mapa é direto: COUNT é o CONT.VALORES, SUM é a SOMA, AVG é a MÉDIA, COUNT(*) FILTER (WHERE ...) faz o papel do CONT.SE e SUM(...) FILTER (WHERE ...) o do SOMASE. Mais comparações no guia SQL vs Excel.

Perguntas frequentes

Quais são as funções de agregação do SQL?

As cinco padrão são COUNT, SUM, AVG, MIN e MAX. A maioria dos bancos acrescenta outras, como STRING_AGG, ARRAY_AGG, STDDEV, VARIANCE e funções de percentil. Todas transformam um conjunto de linhas num único valor, por grupo se houver GROUP BY.

Qual a diferença entre COUNT(*) e COUNT(coluna)?

COUNT(*) conta linhas. COUNT(coluna) conta as linhas em que a coluna não é NULL. Na tabela de voos, eles dão 33.121 e 16.773 para actual_departure, porque os voos que ainda não partiram não têm horário de partida.

As funções de agregação ignoram NULL?

Sim, todas exceto COUNT(*). O AVG divide pelo número de valores não NULL, e o SUM de uma coluna inteiramente NULL (ou de zero linhas) devolve NULL, e não 0.

Posso usar uma função de agregação no WHERE?

Não. O WHERE filtra as linhas antes de elas serem agrupadas, então as agregações ainda não existem. Use HAVING para filtrar por uma agregação, ou calcule-a numa subconsulta e filtre na consulta externa.

Posso usar funções de agregação sem GROUP BY?

Sim. Sem GROUP BY, a tabela inteira (depois do WHERE) é um único grupo, e você recebe uma linha só. Você não pode misturar essas agregações com colunas simples no mesmo SELECT.

Como contar valores distintos no SQL?

COUNT(DISTINCT coluna). Para combinações de várias colunas, conte as linhas de SELECT DISTINCT a, b numa subconsulta, já que COUNT(DISTINCT a, b) só funciona no MySQL.

Outros idiomas: English, Español, Tiếng Việt, 简体中文