Subconsultas

Subconsultas

Imagine que você está em um supermercado e pergunta ao atendente: "Qual é o item mais caro da loja?" O atendente verifica e diz: "É um MacBook Pro." Então você complementa: "Quem são os clientes que compraram esse produto?" — O que você acabou de fazer é uma subconsulta: usar a resposta de uma pergunta para fazer a próxima. Subconsultas no SQL funcionam da mesma forma: usar o resultado de uma consulta como condição ou fonte de dados para outra consulta.


1. Conceitos Fundamentais

O que é uma Subconsulta?

Uma subconsulta (também chamada de consulta interna ou consulta aninhada) é uma consulta embutida dentro de outra instrução SQL. A consulta externa é chamada de consulta principal, e a consulta interna é chamada de subconsulta. A subconsulta é executada primeiro, e seu resultado é usado pela consulta principal.

SQL
-- Subconsulta: encontrar o funcionário com o maior salário
SELECT name, salary
FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees);

Ordem de execução: Primeiro executa SELECT MAX(salary) FROM employees para obter 20000, depois executa a consulta externa para encontrar o(s) funcionário(s) com salário = 20000.

Classificação de Subconsultas

Com base onde a subconsulta aparece, existem três tipos principais:

Tipo Localização Propósito Retorna
Subconsulta WHERE Na cláusula WHERE Filtrar dados Valor único ou lista
Subconsulta FROM (tabela derivada) Na cláusula FROM Atua como tabela temporária Conjunto de resultados
Subconsulta SELECT (subconsulta escalar) Na lista de colunas SELECT Atua como coluna computada Valor único

Subconsulta WHERE

O uso mais comum. A subconsulta aparece na cláusula WHERE, determinando dinamicamente a condição de filtro.

SQL
-- Encontrar funcionários com salário acima da média
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

Subconsultas podem retornar:

SQL
-- Retornar múltiplos valores: correspondência com IN
SELECT name, salary
FROM employees
WHERE department_id IN (
    SELECT id FROM departments WHERE location = 'Beijing'
);

Subconsulta FROM (Tabela Derivada)

A subconsulta aparece na cláusula FROM, efetivamente criando uma tabela temporária (também chamada de tabela derivada). Deve receber um alias.

SQL
-- Primeiro calcular o salário médio de cada departamento, depois filtrar
SELECT dept_name, avg_salary
FROM (
    SELECT d.name AS dept_name, AVG(e.salary) AS avg_salary
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    GROUP BY d.name
) AS dept_avg
WHERE avg_salary > 12000;
💡 Dica: Subconsultas FROM devem ter um alias (como AS dept_avg acima), caso contrário o SQL lançará um erro.

Subconsulta SELECT (Subconsulta Escalar)

A subconsulta aparece na lista de colunas SELECT como uma coluna computada. Cada vez que a consulta principal processa uma linha, a subconsulta é executada uma vez.

SQL
-- Exibir o salário de cada funcionário e o salário médio da empresa
SELECT name, salary,
       (SELECT AVG(salary) FROM employees) AS avg_salary
FROM employees;

Saída:

TEXT
name      salary    avg_salary
--------  --------  ----------
Zhang San 15000.00  14000.00
Li Si     18000.00  14000.00
Wang Wu   12000.00  14000.00
Zhao Liu  13000.00  14000.00
Qian Qi   20000.00  14000.00
Sun Ba    11000.00  14000.00
Zhou Jiu  9000.00   14000.00
Wu Shi    14000.00  14000.00
💡 Dica: Uma subconsulta escalar deve retornar exatamente uma linha e uma coluna, caso contrário lançará um erro. É adequada para adicionar colunas de "informações de resumo".

EXISTS e NOT EXISTS

EXISTS verifica se uma subconsulta retorna pelo menos uma linha. Não se importa com quais valores são retornados — apenas se "existem resultados".

SQL
-- Encontrar departamentos que possuem funcionários
SELECT d.name
FROM departments d
WHERE EXISTS (
    SELECT 1 FROM employees e WHERE e.department_id = d.id
);
SQL
-- Encontrar departamentos sem funcionários
SELECT d.name
FROM departments d
WHERE NOT EXISTS (
    SELECT 1 FROM employees e WHERE e.department_id = d.id
);
💡 Dica: SELECT 1 em EXISTS é uma prática convencional porque EXISTS só se importa com "se as linhas existem", não com os valores das colunas. Usar SELECT * ou SELECT NULL tem o mesmo efeito.

Comparação de Desempenho: Subconsulta vs JOIN

Item de Comparação Subconsulta JOIN
Legibilidade Mais próxima do pensamento em linguagem natural Requer compreensão da lógica de junção
Desempenho Pouca diferença em casos simples; subconsultas correlacionadas podem ser mais lentas Geralmente melhor, com otimizador especializado
Flexibilidade Pode usar resultados de funções de agregação como condições Requer GROUP BY
Recomendado para "Encontrar o maior", "encontrar inexistente" "Exibir com associação", "mesclagem de múltiplas tabelas"
💡 Dica: Bancos de dados modernos (como PostgreSQL, MySQL 8.0) possuem otimizadores inteligentes, e a diferença de desempenho entre subconsultas e JOINs é insignificante em muitos cenários. Priorize melhor legibilidade, e use EXPLAIN para analisar e otimizar quando surgirem problemas de desempenho.


2. Sintaxe/Uso Básico

Sintaxe de Subconsulta WHERE

SQL
-- Subconsulta escalar (retorna valor único)
SELECT nome_coluna FROM tabela
WHERE coluna operador_comparação (SELECT função_agregação FROM tabela);

-- Subconsulta de múltiplas linhas (retorna lista)
SELECT nome_coluna FROM tabela
WHERE coluna IN (SELECT coluna FROM tabela WHERE condição);

Sintaxe de Subconsulta FROM

SQL
SELECT nome_coluna
FROM (SELECT ... FROM ... WHERE ...) AS alias
WHERE condição;
💡 Dica: O alias para uma subconsulta FROM é obrigatório. Diferentes bancos de dados têm requisitos diferentes, mas adicionar um alias é a abordagem mais segura.

Sintaxe de Subconsulta SELECT Escalar

SQL
SELECT coluna1, coluna2,
       (SELECT função_agregação FROM tabela WHERE condição) AS alias
FROM tabela;
💡 Dica: Se uma subconsulta escalar precisar referenciar colunas da consulta externa (subconsulta correlacionada), você pode usar o alias da tabela diretamente da consulta externa.

Sintaxe EXISTS

SQL
SELECT nome_coluna FROM tabela_A a
WHERE EXISTS (SELECT 1 FROM tabela_B b WHERE b.chave_estrangeira = a.chave_primaria);
💡 Dica: EXISTS é uma "subconsulta correlacionada" — a subconsulta referencia colunas da consulta externa (como a.id), então a subconsulta é executada uma vez para cada linha da consulta externa.

💡 Dica: Para grandes conjuntos de dados, EXISTS é tipicamente mais eficiente que IN, porque EXISTS para assim que encontra a primeira correspondência, enquanto IN precisa retornar todos os resultados.


Exemplo: Encontrar Funcionários com Salário Acima da Média (Dificuldade ⭐)

SQL
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees)
ORDER BY salary DESC;
▶ Experimente

Saída:

TEXT
name      salary
--------  --------
Qian Qi   20000.00
Li Si     18000.00
Zhang San 15000.00
Wu Shi    14000.00

Primeiro calcula o salário médio (aproximadamente 14000), depois filtra funcionários acima da média.


Exemplo: Encontrar o Funcionário Mais Bem Pago de Cada Departamento (Dificuldade ⭐⭐)

SQL
SELECT e.name, d.name AS department, e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE e.salary = (
    SELECT MAX(e2.salary)
    FROM employees e2
    WHERE e2.department_id = e.department_id
);
▶ Experimente

Saída:

TEXT
name      department  salary
--------  ----------  --------
Qian Qi   Technology  20000.00
Wang Wu   Marketing   12000.00
Wu Shi    Finance     14000.00
💡 Chave: Esta é uma subconsulta correlacionadae.department_id na subconsulta referencia a linha atual da consulta externa. A subconsulta é executada uma vez para cada linha externa, encontrando o maior salário naquele departamento do funcionário, e então verifica se o funcionário atual é o mais bem pago.


Exemplo: Usar Tabela Derivada para Analisar Níveis Salariais por Departamento (Dificuldade ⭐⭐⭐)

SQL
SELECT dept_name, emp_count, avg_salary,
       CASE 
           WHEN avg_salary >= 15000 THEN 'Alto'
           WHEN avg_salary >= 12000 THEN 'Médio'
           ELSE 'Precisa melhorar'
       END AS nivel
FROM (
    SELECT d.name AS dept_name, 
           COUNT(e.id) AS emp_count,
           AVG(e.salary) AS avg_salary
    FROM departments d
    LEFT JOIN employees e ON d.id = e.department_id
    GROUP BY d.id, d.name
) AS dept_stats
WHERE emp_count > 0
ORDER BY avg_salary DESC;
▶ Experimente

Saída:

TEXT
dept_name  emp_count  avg_salary  nivel
---------  ---------  ----------  ----------------
Technology 3          17666.67    Alto
Finance    2          13500.00    Médio
Marketing  2          11500.00    Precisa melhorar
💡 Abordagem: A subconsulta FROM primeiro calcula a contagem de funcionários e o salário médio de cada departamento, depois a consulta externa classifica e filtra essas estatísticas. Esse padrão de "agregar primeiro, analisar depois" é muito comum no desenvolvimento de relatórios.


3. Casos de Uso Comuns

Caso 1: Encontrar Produtos Nunca Pedidos

SQL
SELECT p.name, p.category, p.price
FROM products p
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.product_id = p.id
);

Saída (depende dos dados):

TEXT
name      category  price
--------  --------  -------
Mac Mini  Computer  4499.00
💡 Comparação: O mesmo requisito implementado com LEFT JOIN:

SQL
SELECT p.name, p.category, p.price
FROM products p
LEFT JOIN orders o ON p.id = o.product_id
WHERE o.id IS NULL;

Ambas as abordagens têm desempenho semelhante — escolha a com melhor legibilidade.

Caso 2: Encontrar Clientes cujo Valor de Pedido Excede a Média

SQL
SELECT customer_name, total_spent
FROM (
    SELECT o.customer_name, 
           SUM(o.quantity * p.price) AS total_spent
    FROM orders o
    JOIN products p ON o.product_id = p.id
    GROUP BY o.customer_name
) AS customer_totals
WHERE total_spent > (
    SELECT AVG(o.quantity * p.price)
    FROM orders o
    JOIN products p ON o.product_id = p.id
);

Saída:

TEXT
customer_name  total_spent
-------------  -----------
Xiao Li        14397.00
Xiao Gang      17998.00
Xiao Wang      11998.00
💡 Abordagem: A subconsulta FROM calcula o gasto total de cada cliente, a subconsulta WHERE calcula o valor médio dos pedidos, e a consulta externa filtra clientes acima da média.


❓ Perguntas Frequentes

P: Quantos níveis de subconsultas podem ser aninhados? R: Não há limite teórico, mas na prática, recomenda-se não exceder 3 níveis. Muitos níveis indicam lógica de consulta excessivamente complexa — considere dividir em múltiplas etapas ou reescrever com JOINs.

P: Devo usar IN ou EXISTS? R: Quando o conjunto de resultados da subconsulta é pequeno e a tabela externa é grande, IN é mais eficiente; quando a tabela externa é pequena e a tabela da subconsulta é grande, EXISTS é mais eficiente (porque EXISTS para na primeira correspondência). A diferença é mínima para cenários simples — use EXPLAIN para comparar em casos complexos.

P: O que acontece se uma subconsulta escalar retornar múltiplas linhas? R: O banco de dados lançará um erro. Uma subconsulta escalar deve retornar exatamente uma linha e uma coluna. Se você não tiver certeza sobre a contagem de linhas, use LIMIT 1 ou uma função de agregação (como MAX, MIN) para garantir um valor único.

P: Qual é a diferença entre subconsultas correlacionadas e não correlacionadas? R: Uma subconsulta não correlacionada é executada independentemente uma vez (por exemplo, "encontrar salário médio"), e seu resultado é usado pela consulta principal. Uma subconsulta correlacionada referencia colunas da consulta externa e é executada uma vez para cada linha externa (por exemplo, "encontrar o maior salário em cada departamento"). Subconsultas correlacionadas podem ser lentas com grandes conjuntos de dados — observe o desempenho.


📖 Resumo


📝 Exercícios

Exercício 1 (⭐): Use uma subconsulta para encontrar todos os funcionários do departamento "Technology" (dica: primeiro use uma subconsulta para encontrar o ID do departamento Technology).

Exercício 2 (⭐⭐): Use uma subconsulta FROM (tabela derivada) para calcular a contagem de pedidos e o gasto total de cada cliente, e então filtre clientes cujo gasto total excede 5000.

Exercício 3 (⭐⭐⭐): Use EXISTS para encontrar "clientes que pediram todos os produtos" — ou seja, clientes para os quais não existe nenhum "produto não pedido" (dica: lógica de duplo NOT EXISTS, ou implemente de outra forma).


Próxima Lição

👉 10-set-operations - Operações com Conjuntos: Aprenda UNION, UNION ALL, INTERSECT e EXCEPT, e domine operações com conjuntos em múltiplos resultados de consultas!

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%