9

Subconsultas (Subqueries)

Avançado • 80 minutos • Consultas aninhadas

Aprenda a criar consultas dentro de consultas para resolver problemas complexos e obter dados específicos de forma eficiente.

Objetivos de Aprendizagem

O que são Subconsultas?

Uma subconsulta (subquery) é uma consulta SQL aninhada dentro de outra consulta. É uma ferramenta poderosa para resolver problemas complexos dividindo-os em partes menores.

🎯 Quando usar subconsultas?

  • Comparações complexas: Encontrar registros acima da média
  • Filtros dinâmicos: Usar resultado de uma consulta como filtro
  • Verificação de existência: Verificar se um registro existe
  • Cálculos aninhados: Usar resultado de uma consulta em outra
  • Alternativa a JOINs: Quando JOIN é complexo demais

Estrutura Básica

SELECT coluna1, coluna2
FROM tabela1
WHERE coluna3 = (SELECT coluna4 FROM tabela2 WHERE condição);

Consulta Externa

A consulta principal que usa o resultado da subconsulta

Subconsulta

A consulta aninhada entre parênteses

Resultado

O valor retornado pela subconsulta

Tipos de Subconsultas

1. Subconsulta Escalar (Retorna um valor)

Retorna exatamente um valor (uma linha, uma coluna).

-- Produtos com preço acima da média
SELECT nome, preco
FROM produtos
WHERE preco > (SELECT AVG(preco) FROM produtos);

Uso: Comparações com valores calculados (média, máximo, mínimo)

2. Subconsulta de Múltiplas Linhas

Retorna múltiplos valores (várias linhas, uma coluna).

-- Produtos das categorias 'Eletrônicos' ou 'Livros'
SELECT nome, preco
FROM produtos
WHERE categoria_id IN (
    SELECT id FROM categorias 
    WHERE nome IN ('Eletrônicos', 'Livros')
);

Operadores: IN, NOT IN, ANY, ALL, EXISTS

3. Subconsulta de Múltiplas Colunas

Retorna múltiplos valores (várias linhas, várias colunas).

-- Produtos com preço máximo por categoria
SELECT nome, categoria_id, preco
FROM produtos
WHERE (categoria_id, preco) IN (
    SELECT categoria_id, MAX(preco)
    FROM produtos
    GROUP BY categoria_id
);

Uso: Comparações com múltiplos critérios

4. Subconsulta Correlacionada

Referencia colunas da consulta externa.

-- Produtos com preço acima da média da sua categoria
SELECT nome, categoria_id, preco
FROM produtos p1
WHERE preco > (
    SELECT AVG(preco)
    FROM produtos p2
    WHERE p2.categoria_id = p1.categoria_id
);

Característica: Executa uma vez para cada linha da consulta externa

Subconsultas no WHERE

O uso mais comum de subconsultas é na cláusula WHERE para filtrar dados.

Operadores com Subconsultas

IN / NOT IN

-- Clientes que fizeram pedidos
SELECT nome
FROM clientes
WHERE id IN (SELECT DISTINCT cliente_id FROM vendas);

-- Produtos nunca vendidos
SELECT nome
FROM produtos
WHERE id NOT IN (
    SELECT DISTINCT produto_id 
    FROM vendas 
    WHERE produto_id IS NOT NULL
);

Cuidado: NOT IN com valores NULL pode retornar resultados inesperados!

EXISTS / NOT EXISTS

-- Clientes que fizeram pedidos (mais eficiente que IN)
SELECT nome
FROM clientes c
WHERE EXISTS (
    SELECT 1 FROM vendas v 
    WHERE v.cliente_id = c.id
);

-- Categorias sem produtos
SELECT nome
FROM categorias c
WHERE NOT EXISTS (
    SELECT 1 FROM produtos p 
    WHERE p.categoria_id = c.id
);

Vantagem: EXISTS para quando você só precisa verificar existência, não os valores

ANY / ALL

-- Produtos mais caros que QUALQUER produto da categoria 1
SELECT nome, preco
FROM produtos
WHERE preco > ANY (
    SELECT preco FROM produtos WHERE categoria_id = 1
);

-- Produtos mais caros que TODOS os produtos da categoria 1
SELECT nome, preco
FROM produtos
WHERE preco > ALL (
    SELECT preco FROM produtos WHERE categoria_id = 1
);

ANY: Verdadeiro se a condição for verdadeira para PELO MENOS um valor
ALL: Verdadeiro se a condição for verdadeira para TODOS os valores

Subconsultas no SELECT

Subconsultas no SELECT permitem calcular valores adicionais para cada linha.

Exemplos Práticos

1. Calculando totais relacionados

-- Lista de clientes com total gasto
SELECT 
    c.nome,
    c.email,
    (
        SELECT COUNT(*) 
        FROM vendas v 
        WHERE v.cliente_id = c.id
    ) as total_pedidos,
    (
        SELECT COALESCE(SUM(valor_total), 0) 
        FROM vendas v 
        WHERE v.cliente_id = c.id
    ) as total_gasto
FROM clientes c;

Cada subconsulta é executada para cada cliente

2. Comparações com médias

-- Produtos com comparação à média da categoria
SELECT 
    p.nome,
    p.preco,
    (
        SELECT AVG(preco) 
        FROM produtos p2 
        WHERE p2.categoria_id = p.categoria_id
    ) as media_categoria,
    p.preco - (
        SELECT AVG(preco) 
        FROM produtos p2 
        WHERE p2.categoria_id = p.categoria_id
    ) as diferenca_media
FROM produtos p;

Compara cada produto com a média da sua categoria

3. Rankings e posições

-- Ranking de produtos por preço na categoria
SELECT 
    nome,
    categoria_id,
    preco,
    (
        SELECT COUNT(*) + 1
        FROM produtos p2
        WHERE p2.categoria_id = p.categoria_id
        AND p2.preco > p.preco
    ) as ranking_preco
FROM produtos p
ORDER BY categoria_id, ranking_preco;

Calcula a posição do produto no ranking de preços da categoria

Performance:

Subconsultas no SELECT podem ser lentas com muitos dados. Considere usar JOINs quando possível.

Subconsultas no FROM (Derived Tables)

Subconsultas no FROM criam "tabelas temporárias" que podem ser usadas como qualquer tabela.

Sintaxe e Exemplos

1. Agregações em múltiplos níveis

-- Top 3 categorias por faturamento
SELECT 
    categoria,
    faturamento_total,
    ranking
FROM (
    SELECT 
        c.nome as categoria,
        SUM(v.valor_total) as faturamento_total,
        ROW_NUMBER() OVER (ORDER BY SUM(v.valor_total) DESC) as ranking
    FROM vendas v
    JOIN produtos p ON v.produto_id = p.id
    JOIN categorias c ON p.categoria_id = c.id
    GROUP BY c.id, c.nome
) as ranking_categorias
WHERE ranking <= 3;

Cria uma tabela temporária com rankings e depois filtra

2. Simplificando consultas complexas

-- Clientes acima da média de gastos
SELECT 
    cliente_nome,
    total_gasto,
    media_geral,
    total_gasto - media_geral as diferenca
FROM (
    SELECT 
        c.nome as cliente_nome,
        COALESCE(SUM(v.valor_total), 0) as total_gasto,
        (
            SELECT AVG(cliente_total.total)
            FROM (
                SELECT SUM(valor_total) as total
                FROM vendas
                GROUP BY cliente_id
            ) as cliente_total
        ) as media_geral
    FROM clientes c
    LEFT JOIN vendas v ON c.id = v.cliente_id
    GROUP BY c.id, c.nome
) as clientes_com_media
WHERE total_gasto > media_geral;

Quebra o problema em etapas menores e mais legíveis

3. Combinando múltiplas fontes

-- Resumo mensal de vendas vs produtos cadastrados
SELECT 
    mes,
    vendas_mes,
    produtos_cadastrados_mes,
    vendas_mes / produtos_cadastrados_mes as vendas_por_produto
FROM (
    SELECT 
        EXTRACT(MONTH FROM data_venda) as mes,
        COUNT(*) as vendas_mes
    FROM vendas
    WHERE EXTRACT(YEAR FROM data_venda) = 2024
    GROUP BY EXTRACT(MONTH FROM data_venda)
) vendas_mensais
JOIN (
    SELECT 
        EXTRACT(MONTH FROM data_cadastro) as mes,
        COUNT(*) as produtos_cadastrados_mes
    FROM produtos
    WHERE EXTRACT(YEAR FROM data_cadastro) = 2024
    GROUP BY EXTRACT(MONTH FROM data_cadastro)
) produtos_mensais USING (mes);

Combina dados de diferentes agregações

Dica:

Use aliases descritivos para subconsultas no FROM. Isso torna o código mais legível.

Exercício Prático

Vamos praticar subconsultas!

Complete as consultas SQL abaixo:

1. Complete a subconsulta para encontrar produtos acima da média:

SELECT nome, preco
FROM produtos
WHERE preco > (______ ______(preco) FROM produtos);

2. Qual operador verifica se um valor existe em uma subconsulta?

3. Complete para encontrar clientes que fizeram pedidos:

SELECT nome
FROM clientes c
WHERE ______ (
    SELECT 1 FROM vendas v 
    WHERE v.cliente_id = c.id
);

4. Qual é a diferença entre ANY e ALL?

Dicas de Performance

Faça

  • • Use EXISTS em vez de IN quando possível
  • • Prefira JOINs para consultas correlacionadas simples
  • • Use índices nas colunas da subconsulta
  • • Limite resultados com WHERE na subconsulta
  • • Use LIMIT quando apropriado

Evite

  • • Subconsultas no SELECT com muitos dados
  • • NOT IN com colunas que podem ter NULL
  • • Subconsultas desnecessariamente complexas
  • • Múltiplas subconsultas quando um JOIN resolve
  • • Subconsultas sem filtros adequados

Exemplo: Otimização

❌ Lento

SELECT *
FROM produtos p
WHERE p.id IN (
    SELECT produto_id
    FROM vendas
    -- Sem filtros!
);

✅ Rápido

SELECT DISTINCT p.*
FROM produtos p
JOIN vendas v ON p.id = v.produto_id
WHERE v.data_venda >= '2024-01-01';

Resumo da Aula

  • Subconsultas são consultas aninhadas dentro de outras consultas
  • Podem ser usadas no WHERE, SELECT e FROM
  • EXISTS é mais eficiente que IN para verificar existência
  • ANY/ALL comparam com múltiplos valores
  • Performance é importante - considere JOINs como alternativa
Aula Anterior: Funções de Agregação Próxima Aula: Índices e Performance