Contexto Pedagógico (A Pergunta de Duas Etapas)
Imagine que a diretoria solicite: "Listar todos os produtos com preço maior que a média da loja".
O erro mais comum dos estudantes é tentar rodar WHERE preco > AVG(preco), o que gera erro imediato porque funções de agregação não podem rodar diretamente na cláusula WHERE sem uma consulta prévia.
SELECT * FROM produtos WHERE preco > AVG(preco);
SELECT * FROM produtos WHERE preco > (SELECT AVG(preco) FROM produtos);
Script de Preparação do Banco (Criação de Tabelas & Dados)
Execute o script abaixo no SQL Editor do Supabase / PostgreSQL para criar as tabelas relacionais (clientes, produtos, pedidos, itens_pedido) e popular a base de testes.
-- =================================================================
-- PASSO 00: SCHEMA & SEED DATA (POSTGRESQL / SUPABASE)
-- =================================================================
-- 1. Limpeza prévia de tabelas
DROP TABLE IF EXISTS itens_pedido CASCADE;
DROP TABLE IF EXISTS pedidos CASCADE;
DROP TABLE IF EXISTS produtos CASCADE;
DROP TABLE IF EXISTS clientes CASCADE;
-- 2. Criação das Tabelas (DDL)
CREATE TABLE clientes (
id_cliente SERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
saldo_pontos INT DEFAULT 0
);
CREATE TABLE produtos (
id_produto SERIAL PRIMARY KEY,
nome_produto VARCHAR(100) NOT NULL,
categoria VARCHAR(50) NOT NULL,
preco DECIMAL(10, 2) NOT NULL
);
CREATE TABLE pedidos (
id_pedido SERIAL PRIMARY KEY,
id_cliente INT REFERENCES clientes(id_cliente),
data_pedido DATE NOT NULL,
valor_total DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) DEFAULT 'Concluído'
);
CREATE TABLE itens_pedido (
id_item SERIAL PRIMARY KEY,
id_pedido INT REFERENCES pedidos(id_pedido),
id_produto INT REFERENCES produtos(id_produto),
quantidade INT NOT NULL,
preco_unitario DECIMAL(10, 2) NOT NULL
);
-- 3. Inserção dos Dados de Teste (DML)
INSERT INTO clientes (nome, email, saldo_pontos) VALUES
('Ana Silva', 'ana.silva@email.com', 6500),
('Carlos Oliveira', 'carlos.o@email.com', 1200),
('Mariana Costa', 'mariana.costa@email.com', 5800),
('Beatriz Lima', 'beatriz.l@email.com', 450),
('Lucas Mendes', 'lucas.mendes@email.com', 0);
INSERT INTO produtos (nome_produto, categoria, preco) VALUES
('Notebook Gamer i9', 'Eletrônicos', 8500.00),
('Monitor Ultrawide 34"', 'Eletrônicos', 3200.00),
('Teclado Mecânico RGB', 'Acessórios', 350.00),
('Mouse Sem Fio Ergonomico', 'Acessórios', 180.00),
('Cadeira Ergonômica', 'Móveis', 1200.00),
('Suporte para Monitor', 'Acessórios', 90.00),
('Fone Noise Cancelling', 'Eletrônicos', 1400.00);
INSERT INTO pedidos (id_cliente, data_pedido, valor_total, status) VALUES
(1, '2026-09-05', 8850.00, 'Concluído'),
(1, '2026-09-15', 350.00, 'Concluído'),
(2, '2026-08-10', 3200.00, 'Concluído'),
(3, '2026-09-20', 1200.00, 'Concluído'),
(3, '2026-09-22', 1400.00, 'Pendente');
INSERT INTO itens_pedido (id_pedido, id_produto, quantidade, preco_unitario) VALUES
(1, 1, 1, 8500.00),
(1, 3, 1, 350.00),
(2, 3, 1, 350.00),
(3, 2, 1, 3200.00),
(4, 5, 1, 1200.00),
(5, 7, 1, 1400.00);
Subconsulta Escalar no WHERE (Retorno de Valor Único)
Subconsultas escalares retornam exatamente 1 linha e 1 coluna (um único valor numerico ou de texto), permitindo a utilização de operadores de comparação padrão (>, <, =, >=, <=).
-- Exemplo 1: Produtos com preço acima da média geral da loja
SELECT
id_produto,
nome_produto,
preco
FROM produtos
WHERE preco > (
SELECT AVG(preco)
FROM produtos
)
ORDER BY preco DESC;
| id_produto | nome_produto | preco |
|---|---|---|
| 105 | Notebook Gamer i9 | R$ 8.500,00 |
| 102 | Monitor Ultrawide 34" | R$ 3.200,00 |
Filtrando Conjuntos com IN e NOT IN
Quando a subconsulta gera uma lista de múltiplos valores (uma coluna com N linhas), utilizamos os operadores IN e NOT IN.
-- Exemplo 2.1: Listar clientes que realizaram pedidos no último mês
SELECT id_cliente, nome, email
FROM clientes
WHERE id_cliente IN (
SELECT DISTINCT id_cliente
FROM pedidos
WHERE data_pedido >= '2026-09-01'
);
-- Exemplo 2.2: Listar produtos que NUNCA foram vendidos
SELECT id_produto, nome_produto
FROM produtos
WHERE id_produto NOT IN (
SELECT DISTINCT id_produto
FROM itens_pedido
WHERE id_produto IS NOT NULL
);
Alta Performance de Busca: EXISTS e NOT EXISTS
O operador EXISTS realiza uma subconsulta correlacionada. Ele aplica o efeito Short-Circuit (Curto-circuito): assim que encontra o primeiro registro correspondente, ele interrompe a busca naquela linha e retorna TRUE, sendo drasticamente mais veloz em grandes bases de dados que o IN.
-- Exemplo 3: Clientes ativos que possuem pelo menos 1 pedido registrado (Usando EXISTS)
SELECT
c.id_cliente,
c.nome
FROM clientes c
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.id_cliente = c.id_cliente
);
Operadores Multivalorados: ANY e ALL
> ALL (...) exige que o valor seja superior a todos os itens retornados pela lista (ou seja, maior que o valor máximo). > ANY (...) requer ser maior que pelo menos um dos valores (maior que o mínimo).
-- Exemplo 4.1 (> ALL): Produtos mais caros do que TODOS os produtos da categoria 'Acessórios'
SELECT nome_produto, preco
FROM produtos
WHERE preco > ALL (
SELECT preco
FROM produtos
WHERE categoria = 'Acessórios'
);
-- Exemplo 4.2 (> ANY): Produtos mais caros do que QUALQUER produto da categoria 'Acessórios'
SELECT nome_produto, preco
FROM produtos
WHERE preco > ANY (
SELECT preco
FROM produtos
WHERE categoria = 'Acessórios'
);
Subconsultas na Cláusula SELECT (Campos Calculados)
Podemos inserir um SELECT interno como se fosse uma nova coluna virtual. Atenção: Essa subconsulta executa linha a linha.
-- Exemplo 5: Exibir clientes e uma coluna com o valor total acumulado em compras
SELECT
c.id_cliente,
c.nome,
(
SELECT COALESCE(SUM(p.valor_total), 0)
FROM pedidos p
WHERE p.id_cliente = c.id_cliente
) AS total_gasto_historico
FROM clientes c
ORDER BY total_gasto_historico DESC;
Refatoração para Arquitetura Limpa com CTEs (Cláusula WITH)
-- Exemplo 6: Refatorando consultas complexas usando CTEs (WITH)
WITH MediaPrecoCategorias AS (
SELECT
categoria,
AVG(preco) AS media_categoria
FROM produtos
GROUP BY categoria
),
ClientesVIP AS (
SELECT id_cliente, nome
FROM clientes
WHERE saldo_pontos > 5000
)
-- Consulta Final unificando as CTEs temporárias:
SELECT
p.nome_produto,
p.categoria,
p.preco,
ROUND(mpc.media_categoria, 2) AS media_da_categoria
FROM produtos p
INNER JOIN MediaPrecoCategorias mpc ON p.categoria = mpc.categoria
WHERE p.preco > mpc.media_categoria
ORDER BY p.categoria, p.preco DESC;
Comparador de Arquitetura de Código SQL
Compare a diferença entre uma Subconsulta Aninhada tradicional ("Código Macarrão") e a versão refatorada em CTE (WITH).
SELECT
c.nome,
c.email
FROM clientes c
WHERE c.id_cliente IN (
SELECT p.id_cliente
FROM pedidos p
WHERE p.valor_total > (
SELECT AVG(p2.valor_total)
FROM pedidos p2
)
AND p.status = 'Concluído'
);
WITH MediaGeralPedidos AS (
SELECT AVG(valor_total) AS valor_medio
FROM pedidos
),
PedidosAcimaDaMedia AS (
SELECT id_cliente
FROM pedidos, MediaGeralPedidos
WHERE valor_total > valor_medio
AND status = 'Concluído'
)
SELECT
c.nome,
c.email
FROM clientes c
WHERE EXISTS (
SELECT 1
FROM PedidosAcimaDaMedia pam
WHERE pam.id_cliente = c.id_cliente
);
Matriz de Performance de Execução no PostgreSQL
| Operador / Técnica | Mecanismo de Busca | Desempenho em Bases Gigantes | Recomendação de Uso |
|---|---|---|---|
| IN (Subconsulta) | Carrega a lista inteira na memória antes do filtro | Lento (Risco de estouro de memória) | Apenas para listas pequenas estáticas |
| EXISTS / NOT EXISTS | Curto-circuito: Interrompe no 1º match encontrado | Extremamente Rápido | Preferência absoluta em validações relacionais |
| WITH (CTE) | Cria tabela temporária em RAM tratada pelo otimizador | Otimizado (Postgres 12+) | Padronização de código corporativo e modularização |
32 Exercícios Individuais Práticos
Cada estudante possui um tema exclusivo e um desafio SQL direcionado.