-- Estrutura básica já conhecida SELECT coluna1, coluna2 FROM tabela WHERE condicao ORDER BY coluna ASC | DESC LIMIT n;
Até agora consultamos linhas individuais. Hoje vamos consultar resumos: quantos registros existem? qual a média? qual o total?
Funções que operam sobre um conjunto de linhas e retornam um único valor resumindo aquele conjunto.
Conta o número de linhas (ou valores não nulos).
Soma todos os valores de uma coluna numérica.
Calcula a média aritmética de uma coluna numérica.
Retorna o menor valor de uma coluna.
Retorna o maior valor de uma coluna.
-- Conta todas as linhas da tabela SELECT COUNT(*) FROM pedido; -- Conta quantos pedidos têm status 'entregue' SELECT COUNT(*) FROM pedido WHERE status = 'entregue'; -- COUNT(coluna): conta apenas valores NÃO NULOS SELECT COUNT(dt_devolucao) FROM emprestimo; -- ^ retorna só os empréstimos que já foram devolvidos -- COUNT(DISTINCT): conta valores únicos SELECT COUNT(DISTINCT id_cliente) FROM pedido; -- ^ quantos clientes distintos fizeram ao menos 1 pedido
Atenção: COUNT(*) conta todas as linhas, incluindo NULLs. COUNT(coluna) ignora NULLs naquela coluna.
-- Valor total de todos os pedidos SELECT SUM(valor_total) AS receita_total FROM pedido; -- Ticket médio dos pedidos SELECT AVG(valor_total) AS ticket_medio FROM pedido; -- Combinar: total e média de pedidos entregues SELECT SUM(valor_total) AS total_entregue, AVG(valor_total) AS media_entregue, COUNT(*) AS qtd_entregue FROM pedido WHERE status = 'entregue';
O alias AS renomeia a coluna no resultado — boa prática para colunas calculadas.
-- Produto mais barato e mais caro SELECT MIN(preco) AS menor_preco, MAX(preco) AS maior_preco FROM produto; -- Data do primeiro e último empréstimo registrado SELECT MIN(dt_emprestimo) AS primeiro, MAX(dt_emprestimo) AS ultimo FROM emprestimo;
MIN e MAX comparam qualquer tipo ordenável: texto (alfabético), data (cronológico), número (numérico).
Todas as funções de agregação ignoram NULL — exceto COUNT(*), que conta linhas independentemente.
GROUP BY divide as linhas em grupos com o mesmo valor na(s) coluna(s) indicada(s). A função de agregação é aplicada a cada grupo separadamente.
-- Quantos pedidos por status? SELECT status, COUNT(*) AS total FROM pedido GROUP BY status; -- Resultado: -- status | total -- ------------+------- -- entregue | 142 -- pendente | 38 -- cancelado | 17
Sem GROUP BY, COUNT(*) retornaria um único número: o total de pedidos.
-- Total de vendas por categoria de produto SELECT categoria, COUNT(*) AS qtd_produtos, AVG(preco) AS preco_medio FROM produto GROUP BY categoria ORDER BY preco_medio DESC; -- Faturamento total por cliente SELECT id_cliente, COUNT(*) AS num_pedidos, SUM(valor_total) AS faturamento FROM pedido GROUP BY id_cliente ORDER BY faturamento DESC LIMIT 5;
Regra do GROUP BY: toda coluna no SELECT que não seja uma agregação deve aparecer no GROUP BY.
HAVING filtra grupos após a agregação — o equivalente do WHERE aplicado ao resultado do GROUP BY.
-- Clientes que fizeram mais de 5 pedidos SELECT id_cliente, COUNT(*) AS total_pedidos FROM pedido GROUP BY id_cliente HAVING COUNT(*) > 5; -- Categorias com preço médio acima de 200 SELECT categoria, AVG(preco) AS preco_medio FROM produto GROUP BY categoria HAVING AVG(preco) > 200 ORDER BY preco_medio DESC;
Filtra linhas individuais antes do agrupamento. Não pode referenciar funções de agregação.
→ "Quero apenas pedidos de 2026."
Filtra grupos depois da agregação. Pode (e deve) referenciar funções de agregação.
→ "Quero grupos com mais de 10 pedidos."
-- Usando WHERE e HAVING juntos SELECT id_cliente, COUNT(*) AS pedidos_2026 FROM pedido WHERE EXTRACT(YEAR FROM dt_pedido) = 2026 -- filtra linhas antes GROUP BY id_cliente HAVING COUNT(*) >= 3 -- filtra grupos depois ORDER BY pedidos_2026 DESC;
Identifica a(s) tabela(s) de origem dos dados.
Filtra as linhas antes do agrupamento.
Agrupa as linhas restantes.
Filtra os grupos formados.
Projeta as colunas e aplica as funções de agregação.
Ordena e limita o resultado final.
Entender essa ordem explica por que WHERE não pode usar agregações — elas ainda não foram calculadas nesse momento.
Discuta com um colega. O objetivo é argumentar, não apenas responder.
Uma query retorna a média de salários por departamento. Um analista quer excluir departamentos com menos de 3 funcionários, pois a média seria "pouco representativa". Como você faria isso? Por que usar HAVING e não WHERE?
COUNT(*) e COUNT(coluna) podem retornar valores diferentes para a mesma tabela. Em que situação isso acontece? Isso é um bug ou comportamento esperado? Quando cada um é o correto a usar?
Use o banco da Aula 7 (biblioteca com livros, membros e empréstimos) no DB Fiddle (PostgreSQL).
Quantos livros há por editora? Quantos membros cadastrados? Quantos empréstimos ainda estão em aberto (dt_devolucao IS NULL)?
Quantos empréstimos cada membro já fez? Liste por nome (ORDER BY total DESC). Qual a média de dias de empréstimo por livro devolvido?
Liste apenas os membros que têm mais de 2 empréstimos. Liste as editoras que publicaram mais de 1 livro no banco. Use HAVING em ambos os casos.