Subqueries: consultas dentro de consultas
1. Introdução às Subqueries
Uma subquery (ou subconsulta) é uma consulta SQL aninhada dentro de outra consulta. Ela permite que você utilize o resultado de uma consulta como entrada para outra, criando uma camada adicional de lógica e filtragem. O propósito principal é resolver problemas que exigem múltiplos passos de consulta em uma única instrução SQL.
A principal diferença entre subqueries e joins está na abordagem: enquanto joins combinam tabelas horizontalmente (colunas lado a lado), subqueries processam resultados verticalmente, onde o resultado de uma consulta alimenta outra. Subqueries são particularmente úteis quando você precisa comparar valores individuais com resultados agregados ou verificar condições de existência.
Vantagens das subqueries:
- Leitura mais intuitiva para certos problemas
- Isolamento lógico de partes da consulta
- Úteis para cálculos que dependem de agregações
Desvantagens:
- Podem ser menos eficientes que joins equivalentes
- Subqueries correlacionadas podem ter performance ruim em grandes volumes
- Legibilidade pode ser prejudicada com muitos aninhamentos
2. Subqueries Escalares
Subqueries escalares retornam exatamente um valor (uma linha e uma coluna). São frequentemente usadas em cláusulas SELECT ou WHERE para comparações diretas.
-- Exemplo: Listar funcionários com salário acima da média
SELECT nome, salario
FROM funcionarios
WHERE salario > (SELECT AVG(salario) FROM funcionarios);
-- Exemplo: Mostrar nome e diferença salarial em relação à média
SELECT nome, salario,
salario - (SELECT AVG(salario) FROM funcionarios) AS diferenca_media
FROM funcionarios;
No primeiro exemplo, a subquery calcula a média salarial de todos os funcionários e a consulta externa filtra apenas aqueles com salário superior. No segundo, a subquery é usada dentro do SELECT para calcular a diferença individual.
3. Subqueries com Múltiplas Linhas
Quando uma subquery retorna uma coluna com várias linhas, utilizamos operadores especiais como IN, NOT IN, ANY e ALL.
-- IN: funcionários que trabalham em departamentos específicos
SELECT nome, departamento_id
FROM funcionarios
WHERE departamento_id IN (SELECT id FROM departamentos WHERE ativo = 1);
-- ANY: funcionários com salário maior que qualquer salário do departamento 5
SELECT nome, salario
FROM funcionarios
WHERE salario > ANY (SELECT salario FROM funcionarios WHERE departamento_id = 5);
-- ALL: funcionários com salário maior que todos do departamento 5
SELECT nome, salario
FROM funcionarios
WHERE salario > ALL (SELECT salario FROM funcionarios WHERE departamento_id = 5);
Cuidado com NULL em NOT IN: Se a subquery retornar qualquer valor NULL, o NOT IN retornará um conjunto vazio. Prefira NOT EXISTS nesses casos.
4. Subqueries na Cláusula FROM (Derived Tables)
Subqueries na cláusula FROM criam tabelas derivadas — resultados temporários que podem ser consultados como se fossem tabelas reais. É obrigatório usar um alias.
-- Exemplo: Média salarial por departamento, filtrando departamentos com mais de 3 funcionários
SELECT d.nome_departamento, dados.media_salarial, dados.total_funcionarios
FROM departamentos d
JOIN (
SELECT departamento_id,
AVG(salario) AS media_salarial,
COUNT(*) AS total_funcionarios
FROM funcionarios
GROUP BY departamento_id
HAVING COUNT(*) > 3
) AS dados ON d.id = dados.departamento_id;
Essa abordagem é excelente para criar agregações aninhadas ou aplicar múltiplos níveis de filtros que seriam complexos com subqueries simples.
5. Subqueries Correlacionadas
Subqueries correlacionadas fazem referência a colunas da consulta externa. Elas são executadas linha a linha pela consulta externa, o que pode impactar a performance.
-- Exemplo: Funcionários com salário acima da média do seu próprio departamento
SELECT f1.nome, f1.salario, f1.departamento_id
FROM funcionarios f1
WHERE f1.salario > (
SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f1.departamento_id
);
Aqui, para cada funcionário da consulta externa, a subquery calcula a média salarial do departamento correspondente. Isso significa que a subquery é executada repetidamente — uma vez para cada linha da tabela externa.
6. EXISTS e NOT EXISTS
EXISTS verifica se uma subquery retorna alguma linha, enquanto NOT EXISTS verifica se retorna nenhuma. A principal diferença para IN é que EXISTS é mais eficiente com subqueries correlacionadas e lida melhor com valores NULL.
-- Exemplo: Clientes que realizaram pelo menos um pedido
SELECT c.nome, c.email
FROM clientes c
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
);
-- Exemplo: Clientes que nunca fizeram pedidos
SELECT c.nome, c.email
FROM clientes c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
);
O SELECT 1 dentro do EXISTS é uma convenção — como o operador apenas verifica existência, o conteúdo selecionado é irrelevante. Isso torna a execução mais rápida que IN, que precisa materializar todos os resultados da subquery.
7. Subqueries na Cláusula HAVING
A cláusula HAVING filtra grupos após a agregação. Subqueries aqui permitem comparar métricas de grupos com valores calculados externamente.
-- Exemplo: Departamentos com salário médio acima da média geral da empresa
SELECT departamento_id, AVG(salario) AS media_salarial
FROM funcionarios
GROUP BY departamento_id
HAVING AVG(salario) > (
SELECT AVG(salario) FROM funcionarios
);
-- Exemplo: Departamentos cujo total de salários é maior que 10% do total geral
SELECT departamento_id, SUM(salario) AS total_salarios
FROM funcionarios
GROUP BY departamento_id
HAVING SUM(salario) > (
SELECT SUM(salario) * 0.1 FROM funcionarios
);
8. Boas Práticas e Performance
Quando evitar subqueries:
- Se a subquery pode ser reescrita como um JOIN com mesma semântica, prefira o JOIN — geralmente mais eficiente
- Subqueries correlacionadas em tabelas grandes podem ser extremamente lentas; considere Common Table Expressions (CTEs) ou tabelas temporárias
- Evite aninhamentos profundos (mais de 2-3 níveis) — prejudicam legibilidade e manutenção
Impacto no plano de execução:
- Subqueries não correlacionadas são executadas uma vez e reutilizadas
- Subqueries correlacionadas executam para cada linha externa — O(n * m) no pior caso
- Use EXPLAIN para analisar o plano de execução e identificar gargalos
Dicas de otimização:
- Prefira EXISTS a IN quando a subquery for grande ou correlacionada
- Use índices nas colunas envolvidas nas condições da subquery
- Considere transformar subqueries complexas em CTEs para melhor legibilidade e, em alguns bancos, melhor planos de execução
-- Exemplo: Refatorando subquery correlacionada para CTE (mais legível e potencialmente mais eficiente)
WITH medias_departamento AS (
SELECT departamento_id, AVG(salario) AS media
FROM funcionarios
GROUP BY departamento_id
)
SELECT f.nome, f.salario, f.departamento_id
FROM funcionarios f
JOIN medias_departamento md ON f.departamento_id = md.departamento_id
WHERE f.salario > md.media;
Referências
- Documentação Oficial MySQL: Subqueries — Documentação completa sobre sintaxe, tipos e exemplos de subqueries no MySQL
- PostgreSQL Documentation: Subqueries — Guia oficial do PostgreSQL sobre subqueries, incluindo tabelas derivadas e EXISTS
- SQL Server: Subquery Fundamentals — Microsoft Learn abordando fundamentos, performance e boas práticas de subqueries no SQL Server
- Use The Index, Luke: Subquery vs JOIN Performance — Artigo técnico comparando performance entre subqueries e joins, com explicações sobre planos de execução
- SQL Tutorial: Correlated Subqueries Explained — Tutorial prático com exemplos detalhados de subqueries correlacionadas e dicas de otimização