🔤 ALIASES (Apelidos de Tabelas)
O que são?
Aliases são apelidos curtos dados às tabelas para facilitar a escrita de queries.
FROM clientes c
^^^^^^^^^ ^
tabela alias
Por quê usar?
- ✅ Escrever menos (
c.nomeao invés declientes.nome) - ✅ Obrigatório em JOINs quando há colunas com mesmo nome
- ✅ Deixa query mais legível
SELECT clientes.nome, pedidos.data_pedido
FROM clientes
JOIN pedidos ON clientes.id = pedidos.cliente_id;
SELECT c.nome, p.data_pedido
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id;
clientes c • pedidos p • produtos pr • itens_pedido ip
🔗 NOTAÇÃO tabela.coluna
O que é?
SELECT c.nome, p.status
^----- ^-------
tabela tabela
coluna coluna
Quando usar?
-- ❌ ERRADO (ambíguo - qual id?):
SELECT id, nome
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id;
-- ERRO: column "id" is ambiguous
-- ✅ CERTO (específico):
SELECT c.id, c.nome
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id;
tabela.coluna para clareza
🎯 SELECT - Seleção de Colunas
Sintaxe Básica
SELECT coluna1, coluna2, coluna3
FROM tabela;
Variações
1. SELECT * (tudo)
SELECT * FROM clientes;
-- ⚠️ Ruim em produção (traz dados desnecessários)
-- ✅ Bom para exploração inicial
2. SELECT específico
SELECT nome, email FROM clientes;
-- ✅ Melhor performance
-- ✅ Mais claro
3. SELECT com alias de coluna
SELECT
nome AS nome_cliente,
email AS email_contato
FROM clientes;
4. SELECT com cálculos
SELECT
nome,
preco,
preco * 0.9 AS preco_com_desconto
FROM produtos;
5. SELECT com funções agregadas
SELECT
COUNT(*) AS total_clientes,
AVG(valor_total) AS ticket_medio,
SUM(valor_total) AS receita_total
FROM pedidos;
🔍 WHERE - Filtros
Sintaxe
SELECT colunas
FROM tabela
WHERE condição;
Operadores
| Operador | Descrição | Exemplo |
|---|---|---|
= |
Igualdade | WHERE cidade = 'São Paulo' |
!= ou <> |
Diferente | WHERE status != 'Cancelado' |
>, <, >=, <= |
Comparação | WHERE preco > 100 |
BETWEEN |
Entre valores | WHERE preco BETWEEN 100 AND 500 |
IN |
Está na lista | WHERE categoria IN ('Eletrônicos', 'Roupas') |
LIKE |
Padrão de texto | WHERE nome LIKE 'João%' |
IS NULL |
É nulo | WHERE email IS NULL |
AND / OR |
Combinar condições | WHERE cidade = 'SP' AND status = 'Pago' |
LIKE - Padrões
% = qualquer sequência de caracteres_ = exatamente 1 caractere
'João%' -- começa com João
'%@gmail.com' -- termina com @gmail.com
'%Silva%' -- contém Silva
'_oão' -- J + oão (4 chars)
🔗 JOIN - Junção de Tabelas
Por quê JOIN existe?
Dados estão espalhados em várias tabelas (normalização). JOIN permite combinar essas informações.
INNER JOIN (mais comum)
SELECT c.nome, p.data_pedido, p.valor_total
FROM clientes c
INNER JOIN pedidos p ON c.id = p.cliente_id;
LEFT JOIN
SELECT c.nome, p.data_pedido
FROM clientes c
LEFT JOIN pedidos p ON c.id = p.cliente_id;
Diagrama Visual
JOIN Múltiplos (3+ tabelas)
SELECT c.nome, p.data_pedido, pr.nome AS produto
FROM clientes c
INNER JOIN pedidos p ON c.id = p.cliente_id
INNER JOIN itens_pedido ip ON p.id = ip.pedido_id
INNER JOIN produtos pr ON ip.produto_id = pr.id;
📊 GROUP BY - Agrupamento
O que faz?
Agrupa linhas com valores iguais e permite calcular totais, médias, contagens, etc.
SELECT
cidade,
COUNT(*) AS total_clientes
FROM clientes
GROUP BY cidade;
🎯 Regra de Ouro
SELECT cidade, COUNT(*)
FROM clientes;
-- ERRO: falta GROUP BY
SELECT cidade, COUNT(*)
FROM clientes
GROUP BY cidade;
O que pode ir no SELECT com GROUP BY?
- ✅ Colunas do GROUP BY
- ✅ Funções agregadas (COUNT, SUM, AVG, MIN, MAX)
- ❌ Colunas normais (não agrupadas)
GROUP BY múltiplas colunas
SELECT
cidade,
status,
COUNT(*) AS total
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id
GROUP BY cidade, status;
🔢 FUNÇÕES AGREGADAS
| Função | O que faz | Exemplo |
|---|---|---|
COUNT(*) |
Conta todas as linhas | COUNT(*) AS total |
COUNT(coluna) |
Conta linhas onde coluna NÃO é NULL | COUNT(email) |
COUNT(DISTINCT) |
Conta valores únicos | COUNT(DISTINCT cidade) |
SUM() |
Soma valores | SUM(valor_total) |
AVG() |
Média aritmética | AVG(preco) |
MIN() |
Valor mínimo | MIN(preco) |
MAX() |
Valor máximo | MAX(preco) |
Exemplo Completo
SELECT
categoria,
COUNT(*) AS total_produtos,
AVG(preco) AS preco_medio,
MIN(preco) AS mais_barato,
MAX(preco) AS mais_caro,
SUM(estoque) AS estoque_total
FROM produtos
GROUP BY categoria;
🎯 HAVING - Filtro em Agregações
WHERE vs HAVING
Filtra ANTES de agrupar
(linhas individuais)
Filtra DEPOIS de agrupar
(grupos resultantes)
Exemplo
-- Cidades com mais de 1000 clientes:
SELECT
cidade,
COUNT(*) AS total
FROM clientes
GROUP BY cidade
HAVING COUNT(*) > 1000;
SELECT cidade, COUNT(*)
FROM clientes
WHERE COUNT(*) > 1000
GROUP BY cidade;
-- ERRO!
SELECT cidade, COUNT(*)
FROM clientes
GROUP BY cidade
HAVING COUNT(*) > 1000;
WHERE + HAVING juntos
SELECT
cidade,
COUNT(*) AS total
FROM clientes
WHERE data_cadastro >= '2024-01-01' -- filtra linhas ANTES
GROUP BY cidade
HAVING COUNT(*) > 100; -- filtra grupos DEPOIS
📋 ORDER BY - Ordenação
Sintaxe
SELECT colunas
FROM tabela
ORDER BY coluna [ASC|DESC];
DESC = Decrescente (Z→A, 9→1)
Exemplos
-- Ordem alfabética (A→Z):
SELECT nome FROM clientes
ORDER BY nome ASC;
-- Ordem inversa (Z→A):
SELECT nome FROM clientes
ORDER BY nome DESC;
-- Múltiplas colunas:
SELECT nome, cidade, preco
FROM produtos
ORDER BY cidade ASC, preco DESC;
-- Ordena por cidade, depois por preço dentro de cada cidade
ORDER BY com agregação
SELECT
cidade,
COUNT(*) AS total
FROM clientes
GROUP BY cidade
ORDER BY total DESC; -- ordena pelo COUNT
🔝 LIMIT - Limitar Resultados
Sintaxe
SELECT colunas
FROM tabela
LIMIT número;
Usos Comuns
-- Top 10:
SELECT nome, preco
FROM produtos
ORDER BY preco DESC
LIMIT 10;
-- Paginação (primeira página):
SELECT * FROM clientes
LIMIT 20 OFFSET 0; -- primeiros 20
-- Segunda página:
SELECT * FROM clientes
LIMIT 20 OFFSET 20; -- próximos 20
Sem ORDER BY, a ordem é imprevisível
🎨 DISTINCT - Valores Únicos
O que faz?
Remove duplicatas do resultado.
SELECT cidade
FROM clientes;
-- São Paulo
-- São Paulo
-- Rio
-- São Paulo
SELECT DISTINCT cidade
FROM clientes;
-- São Paulo
-- Rio
DISTINCT em múltiplas colunas
SELECT DISTINCT cidade, status
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id;
-- Retorna combinações únicas de (cidade, status)
📅 FUNÇÕES DE DATA
DATE_TRUNC - Truncar data
-- Agrupa por mês (ignora dia/hora):
SELECT
DATE_TRUNC('month', data_pedido) AS mes,
COUNT(*) AS total
FROM pedidos
GROUP BY DATE_TRUNC('month', data_pedido);
'year', 'month', 'week', 'day', 'hour'
DATE / TIMESTAMP
-- Data atual:
SELECT CURRENT_DATE; -- 2024-01-15
SELECT CURRENT_TIMESTAMP; -- 2024-01-15 14:30:22
SELECT NOW(); -- mesmo que CURRENT_TIMESTAMP
-- Converter timestamp pra data:
SELECT DATE(data_pedido) FROM pedidos;
Intervalos
-- 30 dias atrás:
SELECT * FROM pedidos
WHERE data_pedido >= CURRENT_DATE - INTERVAL '30 days';
-- 6 meses atrás:
WHERE data_pedido >= CURRENT_DATE - INTERVAL '6 months';
-- 1 ano atrás:
WHERE data_pedido >= CURRENT_DATE - INTERVAL '1 year';
🔄 SUBQUERIES - Queries Dentro de Queries
1. No WHERE (filtrar)
-- Clientes que fizeram pedido:
SELECT nome
FROM clientes
WHERE id IN (
SELECT cliente_id
FROM pedidos
WHERE status = 'Pago'
);
2. No FROM (tabela temporária)
SELECT
categoria,
AVG(total_vendas) AS media_vendas
FROM (
SELECT
pr.categoria,
SUM(ip.quantidade) AS total_vendas
FROM produtos pr
JOIN itens_pedido ip ON pr.id = ip.produto_id
GROUP BY pr.categoria
) AS subquery
GROUP BY categoria;
3. No SELECT (coluna calculada)
SELECT
nome,
(SELECT COUNT(*)
FROM pedidos p
WHERE p.cliente_id = c.id) AS total_pedidos
FROM clientes c;
🧩 CASE - Condicional (IF no SQL)
Sintaxe
CASE
WHEN condição1 THEN resultado1
WHEN condição2 THEN resultado2
ELSE resultado_padrao
END
Exemplo Básico
SELECT
nome,
preco,
CASE
WHEN preco < 100 THEN 'Barato'
WHEN preco BETWEEN 100 AND 500 THEN 'Médio'
ELSE 'Caro'
END AS categoria_preco
FROM produtos;
CASE com Agregação
SELECT
COUNT(CASE WHEN status = 'Pago' THEN 1 END) AS pagos,
COUNT(CASE WHEN status = 'Pendente' THEN 1 END) AS pendentes,
COUNT(CASE WHEN status = 'Cancelado' THEN 1 END) AS cancelados
FROM pedidos;
⚡ ORDEM DE EXECUÇÃO SQL
Entender a ordem de execução explica MUITOS erros comuns.
Estrutura Mental
SELECT [o que quero ver]
FROM [de onde]
JOIN [juntar com outra tabela] ON [condição]
WHERE [filtro antes de agrupar]
GROUP BY [agrupar por]
HAVING [filtro depois de agrupar]
ORDER BY [ordenar]
LIMIT [quantos]
💪 EXERCÍCIOS PRÁTICOS
Use o script de criação de banco de treino fornecido anteriormente.
Nível 1: Básico
Objetivo: Mostrar os 10 clientes que mais gastaram (status = 'Pago')
💡 Ver Solução
SELECT
c.nome,
c.email,
COUNT(p.id) AS total_pedidos,
SUM(p.valor_total) AS total_gasto
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id
WHERE p.status = 'Pago'
GROUP BY c.id, c.nome, c.email
ORDER BY total_gasto DESC
LIMIT 10;
Objetivo: Listar produtos mais vendidos por categoria
💡 Ver Solução
SELECT
pr.categoria,
pr.nome,
SUM(ip.quantidade) AS total_vendido,
SUM(ip.quantidade * ip.preco_unitario) AS receita
FROM produtos pr
JOIN itens_pedido ip ON pr.id = ip.produto_id
JOIN pedidos p ON ip.pedido_id = p.id
WHERE p.status = 'Pago'
GROUP BY pr.categoria, pr.id, pr.nome
ORDER BY pr.categoria, total_vendido DESC;
Objetivo: Vendas dos últimos 6 meses com ticket médio
💡 Ver Solução
SELECT
DATE_TRUNC('month', data_pedido) AS mes,
COUNT(*) AS total_pedidos,
SUM(valor_total) AS receita,
AVG(valor_total) AS ticket_medio
FROM pedidos
WHERE data_pedido >= CURRENT_DATE - INTERVAL '6 months'
AND status = 'Pago'
GROUP BY DATE_TRUNC('month', data_pedido)
ORDER BY mes DESC;
Nível 2: Otimização
Objetivo: Melhorar performance de queries comuns
SELECT nome
FROM clientes
WHERE id IN (
SELECT cliente_id
FROM pedidos
WHERE status = 'Pago'
);
💡 Ver Solução Otimizada
-- JOIN é mais eficiente que subquery IN
SELECT DISTINCT c.nome
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id
WHERE p.status = 'Pago';
Nível 3: Cenários Avançados
Objetivo: Ranking de clientes por cidade
💡 Ver Solução
SELECT
nome,
cidade,
total_pedidos,
RANK() OVER (
PARTITION BY cidade
ORDER BY total_pedidos DESC
) AS ranking_cidade
FROM (
SELECT
c.nome,
c.cidade,
COUNT(p.id) AS total_pedidos
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id
WHERE p.status = 'Pago'
GROUP BY c.id, c.nome, c.cidade
) sub
ORDER BY cidade, ranking_cidade;
Semana 1: Exercícios 1-3 (básico) - 45 min/dia
Semana 2: Exercícios 4-5 (otimização) - 45 min/dia
Semana 3: Exercício 6 (avançado) + revisar tudo - 30 min/dia
🐘 COMANDOS POSTGRES ÚTEIS
Análise de Performance
-- Ver plano de execução:
EXPLAIN SELECT * FROM clientes;
-- Ver plano + tempo real de execução:
EXPLAIN ANALYZE SELECT * FROM clientes;
-- Ver queries rodando agora:
SELECT pid, usename, state, query
FROM pg_stat_activity
WHERE state = 'active';
-- Ver tamanho de tabelas:
SELECT
tablename,
pg_size_pretty(pg_total_relation_size(tablename::text)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(tablename::text) DESC;
Gerenciamento de Índices
-- Listar todos os índices:
SELECT
tablename,
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public';
-- Criar índice:
CREATE INDEX idx_nome ON tabela(coluna);
-- Criar índice composto:
CREATE INDEX idx_nome ON tabela(coluna1, coluna2);
-- Remover índice:
DROP INDEX idx_nome;
-- Reindexar (quando índice fragmentado):
REINDEX TABLE tabela;
Backup & Recovery
-- Backup de um banco (terminal):
docker exec [container] pg_dump -U postgres dbname > backup.sql
-- Restaurar backup:
docker exec -i [container] psql -U postgres dbname < backup.sql
-- Backup de todos os bancos:
docker exec [container] pg_dumpall -U postgres > backup_all.sql
Estatísticas e Manutenção
-- Atualizar estatísticas (importante para performance):
ANALYZE tabela;
-- Limpar espaço morto:
VACUUM tabela;
-- VACUUM completo (trava tabela):
VACUUM FULL tabela;
-- VACUUM + ANALYZE juntos:
VACUUM ANALYZE tabela;
❓ PERGUNTAS COMUNS DE ENTREVISTA
INNER JOIN: Retorna SOMENTE linhas que têm match nas DUAS tabelas.
LEFT JOIN: Retorna TODAS as linhas da tabela da esquerda + matches da direita. Se não houver match, preenche com NULL.
Criar índices em colunas usadas em:
• WHERE (filtros)
• JOIN (chaves estrangeiras)
• ORDER BY (ordenação)
NÃO criar em:
• Tabelas pequenas (< 1000 linhas)
• Colunas com poucos valores distintos (ex: boolean)
• Tabelas com muitos INSERTs (índice desacelera writes)
VACUUM limpa espaço de linhas deletadas/atualizadas. PostgreSQL não remove linhas fisicamente no DELETE/UPDATE, deixa "dead tuples". VACUUM recupera esse espaço e atualiza estatísticas para o query planner.
Atomicity (Atomicidade): Transação é tudo ou nada
Consistency (Consistência): Dados sempre válidos
Isolation (Isolamento): Transações não interferem entre si
Durability (Durabilidade): Commit = dados permanentes
1. Identificar: Uso EXPLAIN ANALYZE pra ver o plano
2. Gargalos: Procuro Seq Scan, Sort, Hash Join caros
3. Índices: Verifico índices faltantes (DMVs/pg_stat)
4. Implementar: Crio índices apropriados
5. Validar: Testo em homolog, comparo tempos
6. Monitorar: Aplico em prod e monitoro impacto
WHERE: Filtra linhas ANTES de agrupar (linhas individuais)
HAVING: Filtra DEPOIS de agrupar (grupos resultantes)
WHERE não pode usar funções agregadas. HAVING pode.
Processo de organizar dados para minimizar redundância e dependências. As 3 formas normais principais:
1NF: Valores atômicos (sem arrays/listas)
2NF: Sem dependências parciais da chave
3NF: Sem dependências transitivas
Na prática, normalizo até 3NF. Às vezes denormalizo propositalmente para performance (ex: tabelas agregadas para relatórios).
📖 GLOSSÁRIO RÁPIDO
| Termo | Significado |
|---|---|
| PK (Primary Key) | Chave primária - identifica linha única |
| FK (Foreign Key) | Chave estrangeira - referencia PK de outra tabela |
| Schema | Estrutura do banco (tabelas, colunas, tipos) |
| Index | Estrutura que acelera buscas (como índice de livro) |
| Query | Consulta SQL (SELECT, INSERT, UPDATE, DELETE) |
| Transaction | Conjunto de operações (tudo ou nada) |
| Commit | Confirma transação (torna permanente) |
| Rollback | Desfaz transação (volta ao estado anterior) |
| Deadlock | 2 transações travadas esperando uma pela outra |
| Lock | Trava em linha/tabela durante modificação |
| Seq Scan | Lê tabela inteira linha por linha (lento) |
| Index Scan | Usa índice para buscar (rápido) |
| Aggregation | Operação que resume dados (COUNT, SUM, AVG) |
| Subquery | Query dentro de outra query |
| CTE | Common Table Expression (WITH clause) |
⚡ COMANDOS RÁPIDOS (CHEAT SHEET)
🔍 Consultas Básicas
SELECT * FROM tabela;
SELECT col1, col2 FROM tabela;
SELECT DISTINCT coluna FROM tabela;
SELECT * FROM tabela WHERE col = 'valor';
SELECT * FROM tabela ORDER BY col DESC;
SELECT * FROM tabela LIMIT 10;
🔗 JOINs
SELECT * FROM t1
INNER JOIN t2 ON t1.id = t2.id;
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id;
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id;
📊 Agregações
SELECT COUNT(*) FROM tabela;
SELECT SUM(coluna) FROM tabela;
SELECT AVG(coluna) FROM tabela;
SELECT MIN(coluna), MAX(coluna) FROM tabela;
SELECT col, COUNT(*)
FROM tabela GROUP BY col;
🔍 Filtros
WHERE col = 'valor'
WHERE col IN ('a', 'b', 'c')
WHERE col BETWEEN 10 AND 50
WHERE col LIKE '%texto%'
WHERE col IS NULL
WHERE col1 = 'a' AND col2 > 10
📅 Datas
CURRENT_DATE
CURRENT_TIMESTAMP
NOW()
DATE_TRUNC('month', coluna)
WHERE data >= CURRENT_DATE - INTERVAL '30 days'
🔧 Índices
CREATE INDEX idx_nome ON tabela(coluna);
CREATE INDEX idx_nome ON tabela(col1, col2);
DROP INDEX idx_nome;
🎯 Você está pronto!
Esta documentação cobre 90% do que cai em entrevistas.
Pratique os exercícios, revise os conceitos e você vai arrasar! 🚀
Dica final: Salve esta página (Ctrl+S) e revise antes das entrevistas.
📚 Criado para treino de entrevistas técnicas em SQL
💪 Pratique, erre, aprenda, repita
🚀 Boa sorte nas entrevistas!