O que você realmente precisa saber sobre subconsultas no dia a dia
Subconsultas aparecem em praticamente toda consulta SQL que exige mais de uma camada de lógica. A maioria dos desenvolvedores as usa sem realmente entender o que o otimizador está fazendo por baixo. Eu passei anos vendo queries que deveriam levar segundos rodarem por minutos por causa de subconsultas mal escritas ou mal estruturadas.
as operações de subconsultas são uma ferramenta poderosa
O poder delas vem da capacidade de embutir resultados temporários dentro de uma consulta maior, sem precisar criar tabelas auxiliares ou CTEs complexas. Funciona assim: você tem uma consulta externa e, dentro dela, insere outra consulta que retorna um valor ou conjunto de valores. O banco processa a subconsulta primeiro e usa o resultado na consulta principal. Por exemplo, imagine que você quer encontrar todos os clientes cujos pedidos totais ultrapassam a média geral. A subconsulta calcula essa média, e a consulta externa filtra os clientes contra esse valor. Simples, direto. Funciona bem quando o volume de dados é razoável.
Mas aí vem o problema real. Em 2019, working em uma empresa de logística, enfrentei uma situação onde uma subconsulta correlacionada estava sendo executada uma vez para cada linha da tabela principal. Era algo como:
SELECT nome, (SELECT SUM(valor) FROM pedidos p WHERE p.cliente_id = c.id) AS total FROM clientes c; Com 50 mil clientes, isso significava 50 mil execuções da subconsulta. A query levou 47 minutos. Quando substituí por um JOIN com GROUP BY, caiu para 3 segundos. A diferença não foi marginal. Foi absurda.
Subconsultas correlacionadas são o tipo mais perigoso porque o otimizador nem sempre consegue reescrevê-las de forma eficiente. Cada vez que a linha externa muda, a subconsulta roda novamente. Se você tem milhões de linhas, isso é catastrófico. Aqui estão algumas nuances que poucos mencionam. Primeiro, subconsultas com IN se comportam de forma diferente dependendo do SGBD. No PostgreSQL, ele converte automaticamente subconsultas IN em semijoins quando é possível, o que é eficiente. No MySQL mais antigo, o comportamento era imprevisível — às vezes fazia nested loop, outras vezes materializava o resultado. Atualizações recentes melhoraram isso, mas ainda depende da versão.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Segundo, subconsultas em cláusulas WHERE com EXISTS são geralmente mais eficientes que IN quando o lado direito pode retornar nulos. Um IN com nulos pode silenciosamente excluir linhas válidas, enquanto EXISTS lida com nulos de forma previsível. Isso me custou uma migração de dados inteira em 2021 porque um relatório semanal estava cortando 12% dos registros sem erro algum. Terceiro, existe um limite prático de aninhamento. Muitos bancos permitem até 32 níveis de subconsultas, mas nenhuma equipe de banco de dados consegue rastrear logicamente algo com mais de três ou quatro níveis. Consultas com aninhamento profundo são ilegíveis e o otimizador sofre para planejar o execution plan correto. Na prática, se você precisa de mais de três níveis, divida em etapas menores usando CTEs ou tabelas temporárias.
Outro ponto importante: subconsultas escalares (aquelas que retornam um único valor) são úteis, mas podem se tornar gargalos se forem chamadas repetidamente. O PostgreSQL tem um recurso chamado "materialization" que ajuda, mas ainda assim, se a subconsulta escalar envolve uma agregação complexa, considere calcular esse valor uma vez e passar como parâmetro. Quando subconsultas funcionam bem. Quando você tem dados moderados, a subconsulta não é correlacionada, e o otimizador consegue transformar tudo em um join eficiente. Nesse cenário, subconsultas são limpas e legíveis. Evitam variáveis de tabela e simplificam o código. Para relatórios rápidos e dashboards internos, elas são perfeitamente adequadas.
Quando você deve evitar. Tabelas grandes com milhões de linhas, subconsultas correlacionadas, e ambientes onde o plano de execução não é visível ou auditável. Se você não consegue ver o execution plan, está confiando cegamente no otimizador, e otimizers falham. Uma alternativa prática para casos complexos é o uso de CTEs (Common Table Expressions). Elas oferecem a mesma legibilidade que subconsultas aninhadas, mas permitem que o otimizador reutilize o resultado em múltiplas partes da query. Em benchmarks que fiz com o PostgreSQL 15, CTEs com materialização explícita (MATERIALIZE) superaram subconsultas correlacionadas em 80% dos casos com datasets acima de 1 milhão de linhas.
Também vale mencionar JSON_TABLE e funções de tabela derivada. Em bancos que suportam, elas substituem subconsultas complexas que antes exigiam parsing manual de strings ou múltiplas junções. Não é bala de prata, mas reduz drasticamente a complexidade em queries que manipulam dados semiestruturados. O conselho mais honesto que posso dar é simples: escreva a query com subconsultas primeiro para clareza, depois examine o plano de execução. Se houver nested loops caros ou escaneamentos de tabela repetidos, refatore. Não adianta ter uma query bonita se ela vai quebrar em produção no primeiro mês de operação real.
Leia o plano de execução. Sempre. É a única forma de saber se sua subconsulta está realmente ajudando ou apenas escondendo um problema.