Parabéns, chegamos naquele ponto em que a coisa fica realmente massa! Hoje vamos ver como combinar vários tipos de subqueries pra resolver problemas mais complexos. EXISTS, IN, HAVING — esse trio vai fazer você se sentir um mago dos bancos de dados. Vamos pegar dados de uma tabela, filtrar usando outra, agrupar, e depois filtrar os grupos. E de quebra, vamos sacar umas dicas pra deixar as queries mais eficientes.
Bora começar definindo um desafio geral, que a gente vai resolvendo ao longo da aula.
Definição do problema
Imagina que temos um banco de dados da universidade com três tabelas:
Tabela students
| id | name | group_id |
|---|---|---|
| 1 | Otto | 101 |
| 2 | Maria | 101 |
| 3 | Alex | 102 |
| 4 | Anna | 103 |
Tabela courses
| id | name |
|---|---|
| 1 | Matemática |
| 2 | Programação |
| 3 | Filosofia |
Tabela enrollments
| student_id | course_id | grade |
|---|---|---|
| 1 | 1 | 90 |
| 1 | 2 | NULL |
| 2 | 1 | 85 |
| 3 | 3 | 70 |
Precisamos selecionar todos os estudantes que:
- Estão matriculados em pelo menos um curso
EXISTS. - Não têm nota em pelo menos um dos cursos em que estão matriculados
IN. - Pertencem a grupos onde a média das notas é maior que 80
HAVING.
Resolvendo com EXISTS e IN
Passo 1: Checar estudantes matriculados (EXISTS). Vamos começar pelo mais simples. Precisamos saber quem está matriculado em pelo menos um curso. Pra isso, rola usar EXISTS.
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE e.student_id = s.id
);
- A query principal pega os nomes da tabela
students. - No subquery, a gente verifica se tem registros na tabela
enrollmentspro estudante do select principal (WHERE e.student_id = s.id). SELECT 1só serve pra dizer que a gente só quer saber se existe registro, não o conteúdo.
Resultado:
| name |
|---|
| Otto |
| Maria |
| Alex |
Agora já sabemos quem está matriculado em algum curso. Mas queremos mais. Queremos filtrar quem não tem nota em algum curso.
Passo 2: Checar ausência de nota (IN + NULL). Agora vamos filtrar: só queremos estudantes que têm pelo menos um curso sem nota. Aqui entra o IN e o truque do NULL.
SELECT name
FROM students s
WHERE id IN (
SELECT e.student_id
FROM enrollments e
WHERE e.grade IS NULL
);
- No select principal, pegamos os nomes dos estudantes.
- O subquery monta uma lista de
student_idda tabelaenrollmentsondegrade IS NULL.
Resultado:
| name |
|---|
| Otto |
Então, Otto é o único estudante que tem um curso sem nota. Que drama! Mas ainda não acabou: só queremos considerar grupos com média acima de 80.
Resolvendo com HAVING
Passo 3: Agrupando e filtrando com HAVING.
Agora é hora de juntar tudo. Precisamos:
- Calcular a média das notas pra cada grupo.
- Filtrar grupos com média acima de 80.
- Mostrar estudantes desses grupos, considerando as condições anteriores.
SELECT name
FROM students s
WHERE s.group_id IN (
SELECT group_id
FROM students
JOIN enrollments ON students.id = enrollments.student_id
WHERE grade IS NOT NULL
GROUP BY group_id
HAVING AVG(grade) > 80
)
AND id IN (
SELECT e.student_id
FROM enrollments e
WHERE e.grade IS NULL
);
- A query principal pega os nomes dos estudantes que passam em todas as condições.
- O primeiro subquery no
WHEREretorna osgroup_iddos grupos com média acima de 80.- Juntamos
studentscomenrollmentspra pegar as notas. - Filtramos só onde
grade IS NOT NULL. - Agrupamos por
group_id. - Usamos
HAVINGpra filtrar os grupos.
- Juntamos
- O segundo subquery no
WHEREcheca se o estudante tem pelo menos um curso sem nota. - As duas condições são combinadas com
AND.
Resultado:
| name |
|---|
| Otto |
Então, descobrimos que Otto não só é o único sem nota, mas também faz parte de um grupo que manda bem nas notas.
Comparando abordagens: EXISTS vs IN
EXISTS é top quando você só quer saber se existe registro. Ele é eficiente porque para de procurar assim que acha o primeiro. Isso faz diferença em tabelas grandes.
Já o IN é útil quando o foco é o conteúdo. Tipo, se você quer uma lista de id pra filtrar depois. Mas cuidado: IN pode ficar lento se o subquery retorna muitos valores.
Quando usar HAVING
Pra dados agregados, quando você precisa filtrar pelo resultado, HAVING é o caminho. Mas se der pra jogar a condição no WHERE (tipo filtrar por coluna), a query fica mais simples e rápida.
Exemplo completo
Pra fixar, bora ver outro exemplo: selecionar grupos onde pelo menos um estudante tem nota menor que 75, mas ninguém está matriculado no curso "Filosofia".
Lembrando das nossas tabelas:
Tabela students
| id | name | group_id |
|---|---|---|
| 1 | Otto | 101 |
| 2 | Maria | 101 |
| 3 | Alex | 102 |
| 4 | Anna | 103 |
Tabela courses
| id | name |
|---|---|
| 1 | Matemática |
| 2 | Programação |
| 3 | Filosofia |
Tabela enrollments
| student_id | course_id | grade |
|---|---|---|
| 1 | 1 | 90 |
| 1 | 2 | NULL |
| 2 | 1 | 85 |
| 3 | 3 | 70 |
SELECT DISTINCT group_id
FROM students s
WHERE group_id IN (
SELECT s.group_id
FROM students s
JOIN enrollments e ON s.id = e.student_id
WHERE e.grade < 75
)
AND group_id NOT IN (
SELECT s.group_id -- subquery de 1º nível
FROM students s
JOIN enrollments e ON s.id = e.student_id
WHERE e.course_id = (
SELECT id FROM courses WHERE name = 'Filosofia' -- subquery de 2º nível :P
)
);
- O primeiro subquery pega os grupos onde tem estudante com nota menor que 75.
- O segundo subquery exclui grupos ligados ao curso "Filosofia".
- Combinamos as condições com
INeNOT INpra chegar no resultado final.
Resultado:
| group_id |
|---|
| 101 |
Isso é útil mesmo?
No mundo real, essas técnicas salvam quando você precisa analisar relações complexas nos dados. Por exemplo:
- Na análise pra separar grupos "especiais" de clientes (VIP, problemáticos, etc).
- No desenvolvimento de sistemas de recomendação, quando você filtra usuários por vários critérios.
- Em entrevistas, quando te pedem pra otimizar uma query SQL cabulosa.
Pratique! Esse é o caminho pra virar ninja.
GO TO FULL VERSION