Subqueries correlacionadas
1. O que são Subqueries Correlacionadas?
Subqueries correlacionadas são consultas aninhadas onde a subconsulta interna faz referência a colunas da consulta externa. Diferentemente das subqueries não correlacionadas — que são executadas uma única vez e produzem um resultado fixo —, as correlacionadas são reavaliadas para cada linha processada pela consulta externa.
Essa característica cria uma dependência direta entre os níveis da consulta. Para cada registro da tabela externa, o banco de dados executa a subconsulta interna utilizando os valores específicos daquela linha. O resultado é um processamento linha a linha, similar ao funcionamento de loops aninhados em linguagens de programação:
PARA CADA linha da tabela externa:
EXECUTE subconsulta usando dados da linha atual
INCLUA linha se condição for satisfeita
O custo computacional é maior que o de subqueries não correlacionadas, mas a flexibilidade obtida justifica o uso em muitos cenários analíticos.
2. Sintaxe e Estrutura Básica
A estrutura típica de uma subquery correlacionada envolve um WHERE na subconsulta que referencia uma coluna da consulta externa. O uso de alias é essencial para evitar ambiguidades entre tabelas de mesmo nome ou estrutura semelhante.
Exemplo mínimo: encontrar funcionários com salário acima da média do seu departamento.
SELECT f.nome, f.salario, f.departamento_id
FROM funcionarios f
WHERE f.salario > (
SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id
);
Aqui, f.departamento_id na subconsulta referencia a tabela externa funcionarios f. Para cada funcionário, o banco calcula a média salarial do seu departamento específico e compara com o salário individual. Sem o alias f2, haveria conflito de referência.
3. Subqueries Correlacionadas com EXISTS e NOT EXISTS
O operador EXISTS é particularmente útil com subqueries correlacionadas, pois verifica apenas a existência de ao menos um registro que satisfaça a condição, sem precisar retornar dados propriamente ditos.
Exemplo: clientes que nunca fizeram pedido.
SELECT c.id, c.nome, c.email
FROM clientes c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
);
A subconsulta retorna 1 (ou qualquer valor) se existir ao menos um pedido para aquele cliente. O NOT EXISTS inverte a lógica, selecionando apenas clientes sem pedidos. Essa abordagem é semanticamente clara e, em muitos bancos, otimizada para usar índices.
O EXISTS correlacionado também é útil para encontrar registros que possuem relacionamentos específicos:
SELECT p.id, p.titulo
FROM produtos p
WHERE EXISTS (
SELECT 1
FROM itens_pedido ip
WHERE ip.produto_id = p.id
AND ip.quantidade > 10
);
4. Subqueries Correlacionadas no SELECT e HAVING
Subqueries correlacionadas podem aparecer na lista de colunas do SELECT, criando colunas calculadas dependentes de cada linha. Também podem ser usadas no HAVING para filtrar grupos com base em subconsultas.
Exemplo no SELECT: exibir o salário de cada funcionário e a diferença percentual em relação à média do seu departamento.
SELECT f.nome,
f.salario,
(SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id) AS media_dept,
ROUND((f.salario - (SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id)) /
(SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id) * 100, 2) AS dif_percentual
FROM funcionarios f;
Exemplo no HAVING: listar departamentos com mais funcionários que a média global de funcionários por departamento.
SELECT d.nome, COUNT(f.id) AS total_funcionarios
FROM departamentos d
LEFT JOIN funcionarios f ON f.departamento_id = d.id
GROUP BY d.id, d.nome
HAVING COUNT(f.id) > (
SELECT AVG(total)
FROM (
SELECT COUNT(*) AS total
FROM funcionarios
GROUP BY departamento_id
) AS sub
);
5. Desempenho e Cuidados com Subqueries Correlacionadas
O principal ponto de atenção é o desempenho. Uma subquery correlacionada é executada uma vez para cada linha da consulta externa. Se a tabela externa tem 10.000 linhas, a subconsulta será executada 10.000 vezes. Em tabelas grandes, isso pode se tornar proibitivo.
Estratégias para mitigar o impacto:
- Indexação adequada: criar índices nas colunas usadas na condição de correlação (ex.:
departamento_idno exemplo anterior) reduz drasticamente o custo de cada execução. - Comparação com JOINs: muitas subqueries correlacionadas podem ser reescritas como
JOINcom agregação, que geralmente são mais eficientes:
-- Versão com JOIN (mais eficiente que subquery correlacionada)
SELECT f.nome, f.salario, f.departamento_id
FROM funcionarios f
JOIN (
SELECT departamento_id, AVG(salario) AS media
FROM funcionarios
GROUP BY departamento_id
) medias ON medias.departamento_id = f.departamento_id
WHERE f.salario > medias.media;
- CTEs (Common Table Expressions) também podem melhorar a legibilidade e, em alguns casos, o plano de execução.
6. Subqueries Correlacionadas vs. Alternativas
Subqueries correlacionadas não são sempre a pior escolha. Existem cenários onde são a solução mais natural ou até inevitáveis.
| Situação | Abordagem recomendada |
|---|---|
| Comparação com agregado por grupo | JOIN com subconsulta agregada (mais eficiente) |
| Verificação de existência (sem dados da subconsulta) | EXISTS correlacionado (pode ser mais rápido que JOIN com DISTINCT) |
| Coluna calculada dependente da linha atual | Subquery correlacionada no SELECT (solução direta) |
| Atualizações condicionais complexas | Subquery correlacionada no UPDATE (caso de uso específico) |
O LATERAL JOIN (suportado por PostgreSQL, Oracle, MySQL 8.0+) é uma alternativa moderna que permite referenciar colunas de tabelas anteriores no FROM, combinando flexibilidade de subqueries correlacionadas com desempenho de joins:
SELECT f.nome, f.salario, medias.media
FROM funcionarios f,
LATERAL (
SELECT AVG(salario) AS media
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id
) medias
WHERE f.salario > medias.media;
7. Exemplos Avançados e Casos de Uso Reais
Atualização condicional: aumentar salário dos funcionários que ganham abaixo da média do seu departamento.
UPDATE funcionarios f
SET salario = salario * 1.10
WHERE salario < (
SELECT AVG(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id
);
Deleção de registros órfãos: remover pedidos cujos clientes foram excluídos.
DELETE FROM pedidos p
WHERE NOT EXISTS (
SELECT 1
FROM clientes c
WHERE c.id = p.cliente_id
);
Ranking por grupo: numerar funcionários por salário dentro de cada departamento (sem usar funções janela).
SELECT f.nome, f.salario, f.departamento_id,
(SELECT COUNT(*)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id
AND f2.salario >= f.salario) AS ranking
FROM funcionarios f
ORDER BY f.departamento_id, ranking;
Diferença percentual em relação ao melhor do departamento:
SELECT f.nome, f.salario, f.departamento_id,
ROUND((f.salario - (SELECT MAX(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id)) /
(SELECT MAX(f2.salario)
FROM funcionarios f2
WHERE f2.departamento_id = f.departamento_id) * 100, 2) AS diff_percentual_max
FROM funcionarios f;
8. Boas Práticas e Debugging
Para testar subqueries correlacionadas isoladamente, substitua a referência externa por um valor literal e execute a subconsulta sozinha:
-- Testando a subconsulta com um valor fixo (departamento_id = 5)
SELECT AVG(salario)
FROM funcionarios
WHERE departamento_id = 5;
Dicas de legibilidade:
- Use alias descritivos:
funcionarios_extefuncionarios_intem vez defef2. - Indente corretamente a subconsulta para destacar sua dependência.
- Comente a lógica quando a correlação não for óbvia.
Erros comuns:
- Referência ambígua: esquecer alias e causar ambiguidade entre colunas de mesmo nome.
- Escopo de colunas: tentar referenciar colunas da subconsulta no nível externo (não permitido).
- Subquery retorna múltiplas linhas em contexto de valor único: usar
EXISTSou funções agregadas para evitar erro.
Subqueries correlacionadas são uma ferramenta poderosa no SQL, oferecendo flexibilidade para consultas dependentes do contexto de cada linha. Dominar seu uso — e saber quando optar por alternativas — é essencial para escrever consultas eficientes e elegantes.
Referências
- PostgreSQL Documentation: Subqueries — Documentação oficial do PostgreSQL sobre subqueries, incluindo correlacionadas e EXISTS.
- MySQL Documentation: Subqueries — Manual de referência MySQL com exemplos de subqueries correlacionadas e dicas de otimização.
- SQL Server: Correlated Subqueries — Guia da Microsoft sobre subqueries correlacionadas no SQL Server, com exemplos práticos.
- Oracle Live SQL: Subquery Fundamentals — Tutorial interativo da Oracle sobre subqueries, incluindo correlacionadas e LATERAL JOIN.
- Use The Index, Luke: Subquery Performance — Artigo técnico sobre desempenho de subqueries e estratégias de indexação para otimização.