CodeGym /Cursos /SQL SELF /Usando subqueries no HAVING para filtrar dados agregados

Usando subqueries no HAVING para filtrar dados agregados

SQL SELF
Nível 14 , Lição 2
Disponível

Às vezes, não basta só agrupar os dados e filtrar o resultado, mas fazer isso levando em conta uma lógica extra — tipo comparar a média das notas dos alunos de um grupo com algum critério externo. É aí que entra o HAVING com subqueries — uma ferramenta poderosa que te ajuda a tomar decisões mais inteligentes direto no SQL.

Relembrando o HAVING

Bora focar nas subqueries usadas junto com o HAVING, pra filtrar dados no nível dos valores agregados. Por quê? Porque o WHERE filtra linhas individuais, mas o HAVING já pega os dados agrupados — é outro nível de análise, que amplia suas possibilidades.

Antes de mergulhar na mistura de subqueries e HAVING, bora refrescar a memória sobre o que é o HAVING e como ele é diferente do WHERE.

  • WHERE filtra linhas antes da agregação (GROUP BY).
  • HAVING filtra dados depois da agregação, quando os dados já estão agrupados.

Imagina que você tá analisando alunos e suas notas. Com WHERE você pode tirar fora alunos com notas mínimas, mas com HAVING dá pra excluir grupos inteiros de alunos com base na média ou nota máxima deles.

Exemplo de dados

Aqui vai uma tabela de exemplo com alunos:

Tabela students:

student_id student_name department grade
1 Alex Physics 80
2 Maria Physics 85
3 Dan Math 90
4 Lisa Math 60
5 John History 70

Exemplo de uso do HAVING (sem subqueries)

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;

Resultado:

department avg_grade
Physics 82.5
Math 75.0

O departamento "History" ficou de fora porque a média dele é menor que 75. Moleza, né? Agora bora colocar um pouco de mágica com subqueries. No próximo exemplo, dá pra filtrar comparando com a média geral de todos os departamentos.

Subqueries no HAVING

Subqueries no HAVING são uma ótima forma de deixar o filtro de dados agregados mais flexível. Dá pra comparar agregados, tipo média ou máximo, com valores calculados de outras partes do banco. Resumindo: dá pra checar "Será que nosso resultado tá melhor que a média geral?"

Exemplo: filtrando departamentos pela média das notas

Vamos supor que você quer achar os departamentos onde os alunos mandam melhor — ou seja, a média das notas do departamento é maior que a média geral da universidade.

Aqui estão nossos dados:

Tabela students:

student_id student_name department grade
1 Alex Physics 80
2 Maria Physics 85
3 Dan Math 90
4 Lisa Math 60
5 John History 70

Primeiro, bora pegar a média das notas de todos os alunos:

SELECT AVG(grade) AS university_avg
FROM students;

Agora vamos usar uma subquery no HAVING:

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);

Resultado:

department avg_grade
Physics 82.5

O que tá rolando aqui?

  1. A subquery (SELECT AVG(grade) FROM students) calcula a média geral — nesse caso, 77.
  2. A query principal agrupa os alunos por departamento e calcula a média de cada um.
  3. O HAVING compara a média do departamento com a geral e só deixa passar quem tá acima.

Comparando WHERE e HAVING

Pra sacar a diferença, imagina que você quer selecionar só os alunos com notas acima da média. Isso dá pra fazer só com WHERE:

SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);

Resultado (usando a tabela dos exemplos anteriores):

name grade
Alex 80
Maria 85
Dan 90

Agora, se você quiser ver em quais departamentos a média dos alunos é maior que a média da universidade, aí só com HAVING — porque você tá filtrando grupos, não linhas:

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);

Resultado:

department avg_grade
Physics 82.5

Resumindo:

  • WHERE trabalha com linhas individuais antes do agrupamento.
  • HAVING filtra grupos depois que eles já foram agregados.

Exemplo: trabalhando com múltiplos agregados

Bora ver outro caso. Suponha que temos a tabela students com as notas dos alunos e seus departamentos:

Tabela students:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History

Agora queremos achar os departamentos onde:

  1. A média das notas dos alunos é maior que a média geral da universidade.
  2. A nota máxima do departamento é maior que 90.

Pra isso, monta esse query:

SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
   AND MAX(grade) > 90;

O que tá rolando nesse query:

  • AVG(grade) > (SELECT AVG(grade) FROM students) — checa se o departamento é mais forte que a média.
  • MAX(grade) > 90 — quer dizer que tem alguém lá que detonou na prova.

Resultado:

department avg_grade max_grade
Math 92.5 95

O departamento "Math" foi o único que tem média acima da geral e ainda um aluno que tirou mais de 90.

Exemplo: selecionando grupos com menor desvio

Imagina que você quer achar grupos onde a diferença entre a maior e a menor nota dos alunos é menor que a diferença geral da universidade.

Aqui está a tabela students que vamos usar:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History

Vamos dividir a tarefa em etapas:

  1. Primeiro, calcula a diferença máximo-mínimo da universidade toda:
    SELECT MAX(grade) - MIN(grade) AS range_university
    FROM students;
    
  2. Agora monta a query principal e junta com essa subquery:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );

Resultado da query:

department range_department
Physics 5
Math 5

Os grupos "Physics" e "Math" tiveram notas mais estáveis — o desvio deles é menor que o da universidade toda.

Otimização de queries com HAVING e subqueries

Lembra que subqueries aninhadas podem pesar bastante na performance, principalmente em bancos grandes. Aqui vão umas dicas:

Use índices. Se a subquery roda em uma coluna que aparece no WHERE ou JOIN, garante que tem índice nela.

Evite excesso de dados. Se a subquery retorna muita coisa intermediária, divide em etapas ou usa tabelas temporárias.

Profile os queries com EXPLAIN. Sempre veja como o PostgreSQL executa seu query. Se perceber que a subquery roda várias vezes, pensa em otimizar.

Compare com CTE. Em alguns casos, usar WITH (Common Table Expressions) pode ser mais rápido e fácil de ler. Mas isso é papo pra próxima aula :P

Combinando subqueries, HAVING e GROUP BY

Com subqueries no HAVING dá pra montar filtros mais complexos, principalmente quando precisa considerar agregados, médias e outras métricas ao mesmo tempo. Isso tudo ajuda a achar insights maneiros nos dados reais.

Exemplo: comparando departamentos por média e número de alunos

Vamos supor que você quer selecionar departamentos onde:

  1. A média das notas é maior que a média da universidade.
  2. O número de alunos é maior que o do departamento com a menor média.

Aqui está a tabela students:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History
Oleg 60 History

Query:

SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
   AND COUNT(*) > (
       SELECT COUNT(*)
       FROM students
       GROUP BY department
       ORDER BY AVG(grade)
       LIMIT 1
   );

Esse query mostra como dá pra combinar subqueries no HAVING e GROUP BY pra analisar vários critérios de uma vez. Resultado:

department avg_grade student_count
Physics 82.5 2
Math 92.5 2

O departamento History ficou de fora porque tem a menor média e menos alunos. Physics e Math — os dois estão acima da média tanto nas notas quanto na quantidade de alunos.

Erros comuns e como evitar

Erro com NULL. Se os dados têm NULL, subqueries com HAVING podem dar resultados estranhos. Usa COALESCE pra tratar esses casos:

SELECT AVG(grade)
FROM students 
WHERE grade IS NOT NULL;

Dados em excesso na subquery. Se a subquery retorna coisa demais, isso pesa na performance. Sempre deixa as condições da subquery bem específicas.

Entender errado a ordem de execução. Lembra que o HAVING roda depois do agrupamento, mas as subqueries podem rodar antes da query principal.

Falta de índices. Se as colunas usadas na subquery não têm índice, o query pode ficar bem lento.

Subqueries no HAVING abrem um monte de possibilidades pra analisar dados no nível dos agregados. Dá pra filtrar grupos com condições complexas, comparar resultados entre grupos e criar queries analíticos avançados. Parabéns, agora você tá pronto pra usar isso nos projetos de verdade!

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION