icardb
SQL essencial para desenvolvedores: do SELECT à performance em 2026
Voltar para artigosPROGRAMAÇÃO

SQL essencial para desenvolvedores: do SELECT à performance em 2026

Por Equipe Editorial Icardb 7 min de leitura

Conteúdo educativo. Exemplos testados em PostgreSQL 16. Conceitos aplicáveis a MySQL 8+, SQL Server 2022 e SQLite 3.45+. Para Oracle, adapte sintaxe de LIMIT/OFFSET (ROWNUM/FETCH FIRST).

SQL é a linguagem que sobreviveu a 5 décadas de hype. NoSQL, document stores, vector DBs — todos coexistem, mas o modelo relacional continua sendo o padrão de fato para dados transacionais. O problema é que a maioria dos devs aprende SQL superficialmente: sabe fazer SELECT * e um INNER JOIN básico, mas trava quando o explain analyze mostra um seq scan em tabela de milhões de linhas. Este guia cobre o que realmente importa do dia a dia.

O modelo relacional em 60 segundos

Dados organizados em tabelas (relações), linhas (tuplas) e colunas (atributos). Chave primária identifica unicamente cada linha. Chaves estrangeiras criam vínculos entre tabelas. A integridade referencial garante que você não apague um cliente que ainda tem pedidos — a menos que programe um ON DELETE CASCADE.

ConceitoAnalogiaSQL
TabelaPlanilhaCREATE TABLE
LinhaRegistro / tuplaINSERT / UPDATE
ColunaCampo / atributoSELECT coluna
Chave primáriaCPF / UUID únicoPRIMARY KEY
Chave estrangeiraReferência cruzadaFOREIGN KEY ... REFERENCES
ÍndiceÍndice remissivo de livroCREATE INDEX

SELECT que não engasga

O SELECT é a instrução mais usada e a mais perigosa. SELECT * em tabela grande é um tiro no pé: trasfega dados desnecessários pela rede, aumenta IO e polui cache. Especifique colunas.

sql
-- ruim: traz 47 colunas quando só precisa de 3
SELECT * FROM pedidos WHERE status = 'pendente';

-- bom: só o necessário, índice cobertor possível
SELECT id, cliente_id, total, created_at
FROM pedidos
WHERE status = 'pendente'
  AND created_at > CURRENT_DATE - INTERVAL '7 days'
ORDER BY created_at DESC
LIMIT 50;

Use EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) antes de otimizar. O PostgreSQL mostra custo estimado, tempo real, tipo de scan (sequential x index) e se houve temp files em disco.

JOINs explicados visualmente

JOIN é a operação mais mal-entendida por iniciantes. A regra mnemônica é: INNER JOIN = interseção, LEFT JOIN = tabela da esquerda + matches da direita, RIGHT JOIN = espelho do LEFT (raramente usado), FULL OUTER JOIN = união.

sql
-- INNER JOIN: só clientes que fizeram pedidos
SELECT c.nome, p.total
FROM clientes c
INNER JOIN pedidos p ON p.cliente_id = c.id;

-- LEFT JOIN: todos os clientes, com ou sem pedido
SELECT c.nome, COALESCE(SUM(p.total), 0) AS total_gasto
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nome;

-- anti-join: clientes que NUNCA compraram (idioma LEFT JOIN + IS NULL)
SELECT c.nome
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
WHERE p.id IS NULL;

CROSS JOIN (produto cartesiano) explode o resultado: 1.000 clientes x 500 produtos = 500.000 linhas. Use só quando realmente precisar de todas as combinações. Self-join une uma tabela com ela mesma — útil para hierarquias (org_chart, árvore de categorias).

Agrupamento e filtros pós-agregação

GROUP BY quebra o resultado em subconjuntos. WHERE filtra linhas antes da agregação; HAVING filtra grupos depois. A ordem lógica de execução é: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

sql
-- top 10 clientes que gastaram mais de R$ 5.000 nos últimos 90 dias
SELECT c.nome, SUM(p.total) AS total
FROM clientes c
JOIN pedidos p ON p.cliente_id = c.id
WHERE p.created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY c.id, c.nome
HAVING SUM(p.total) > 5000
ORDER BY total DESC
LIMIT 10;

Índices: quando funcionam e quando mentem

Índices aceleram leitura e desaceleram escrita (INSERT/UPDATE/DELETE precisam manter a estrutura ordenada). Um índice B-tree — o padrão — é eficiente para igualdade (=) e intervalos (>, <, BETWEEN). Não adianta criar índice em coluna com pouca cardinalidade (booleano, status com 3 valores).

Tipo de índiceQuando usarExemplo
B-tree (padrão)Igualdade, range, ORDER BYCREATE INDEX ON pedidos(created_at)
HashApenas igualdade (raro no PG)CREATE INDEX ... USING hash
GiSTDados geométricos, full-textPostGIS, pg_trgm
GINArrays, JSONB, full-text searchCREATE INDEX ... USING gin(jsonb_col)
BRINDados ordenados sequencialmente grandesLogs, time-series
Índice parcialFiltrar subconjunto frequenteWHERE status = 'ativo'
Índice de expressãoBusca em função aplicadaLOWER(email)

Índice cobertor (covering index) inclui colunas extras no nível folha, evitando lookup na tabela heap. Em PostgreSQL: CREATE INDEX idx ON tabela(col_a) INCLUDE (col_b, col_c).

sql
-- verificar se um índice está sendo usado
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, email FROM usuarios WHERE email = 'teste@exemplo.com';

-- índice funcional para busca case-insensitive
CREATE INDEX idx_usuarios_email_lower ON usuarios(LOWER(email));

-- índice parcial para pedidos pendentes (supõe que 95% já estão entregues)
CREATE INDEX idx_pedidos_pendentes ON pedidos(created_at)
WHERE status = 'pendente';

Transações ACID e níveis de isolamento

Uma transação é uma unidade de trabalho indivisível: ou tudo acontece (COMMIT) ou nada acontece (ROLLBACK). ACID = Atomicidade, Consistência, Isolamento, Durabilidade. O isolamento é o mais sutil: dependendo do nível, leituras sujas, não-repetíveis e fantasmas podem ocorrer.

Nível de isolamentoDirty readNon-repeatablePhantom
READ UNCOMMITTEDSimSimSim
READ COMMITTEDNãoSimSim
REPEATABLE READNãoNãoSim*
SERIALIZABLENãoNãoNão

* PostgreSQL implementa REPEATABLE READ como snapshot isolation, que de fato previne phantoms na prática. SERIALIZABLE é o mais seguro, mas pode gerar serialization_failure — seu código precisa tratar e retry.

sql
BEGIN;

UPDATE contas SET saldo = saldo - 100 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;

-- se algo der errado antes do commit, faça ROLLBACK
COMMIT;

CTEs e subqueries: legibilidade primeiro

Common Table Expressions (WITH) tornam queries complexas legíveis. Diferente de subquery inline, a CTE nomeia uma etapa intermediária. Atenção: no PostgreSQL, CTEs materiais são barreiras de otimização — o planner não consegue empurrar predicados para dentro delas. Use com moderação em queries críticas de performance.

sql
WITH pedidos_recentes AS (
  SELECT cliente_id, SUM(total) AS total
  FROM pedidos
  WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY cliente_id
  HAVING SUM(total) > 1000
),
clientes_ativos AS (
  SELECT id, nome
  FROM clientes
  WHERE status = 'ativo'
)
SELECT c.nome, pr.total
FROM clientes_ativos c
JOIN pedidos_recentes pr ON pr.cliente_id = c.id
ORDER BY pr.total DESC;

Window functions: ranking e agregação por janela

Window functions computam sobre um conjunto de linhas relacionadas à linha atual (a "janela"), sem colapsar o resultado como GROUP BY faz. Útil para ranking, média móvel, comparar com anterior/próximo.

sql
SELECT
  categoria,
  produto,
  preco,
  RANK() OVER (PARTITION BY categoria ORDER BY preco DESC) AS rank_categoria,
  AVG(preco) OVER (PARTITION BY categoria) AS media_categoria,
  preco - LAG(preco) OVER (ORDER BY preco) AS diff_anterior
FROM produtos;

Normalização: até onde ir?

1NF elimina grupos repetitivos (nada de arrays ou CSV em células). 2NF elimina dependências parciais da chave. 3NF elimina dependências transitivas. A regra prática: normalize até 3NF, desnormalize com propósito quando a leitura for dominante e medida. Denormalização precoce é preguiça; normalização obsessiva é vício.

Exemplo de desnormalização justa: cache de total de pedidos por cliente em coluna calculada, atualizado por trigger ou batch, para evitar SUM() agregado a cada request do dashboard.

Erros que custam caro em produção

  • Não usar LIMIT em SELECT exploratório em tabela grande — pode travar o servidor.
  • UPDATE/DELETE sem WHERE: se o cliente de banco não tem safe mode, é desastre imediato. Sempre rode SELECT com a mesma cláusula WHERE antes de atualizar.
  • SELECT COUNT(*) em tabela com milhões de linhas no PostgreSQL é lento (faz seq scan). Use pg_class.reltuples como estimativa ou contadores auxiliares.
  • Lock escalation implícito: transações longas seguram locks, bloqueando writes concorrentes. Mantenha transações curtas.
  • Não usar prepared statements em loops: parse e plan repetidos consomem CPU. Use prepared statements ou ORM com batch insert.
  • N+1 queries: ORMs mal configurados disparam uma query por relacionamento. Use JOIN, select_related ou data loader.

Perguntas frequentes

+PostgreSQL ou MySQL para projetos novos em 2026?

PostgreSQL é o default recomendado: tipos avançados (JSONB, arrays, ranges), extensões ricas (PostGIS, pgvector), melhor conformidade com SQL padrão e licença permissiva (PostgreSQL License). MySQL 8+ é viável, especialmente se a infraestrutura já for Oracle/MySQL-first (ex: RDS barato). MariaDB divergiu do MySQL em features recentes.

+Quando usar NoSQL em vez de SQL?

Quando o modelo é documento naturalmente variável (logs, CMS headless), quando precisa de escalabilidade horizontal automática sem sharding manual (MongoDB Atlas, DynamoDB), ou quando o padrão de acesso é puramente chave-valor com baixa latência (Redis, ScyllaDB). Relacional continua sendo a escolha padrão para dados transacionais com integridade forte.

+O que é um deadlock e como evitar?

Deadlock ocorre quando duas transações seguram locks que a outra precisa. PostgreSQL detecta e aborta uma delas (deadlock_timeout, padrão 1s). Para evitar: acesse recursos sempre na mesma ordem, mantenha transações curtas e use locking pessimista (SELECT FOR UPDATE) só quando necessário.

+Como fazer backup seguro de PostgreSQL?

pg_dump para backups lógicos (SQL ou custom format). Para bases grandes (> 100 GB), prefira pg_basebackup (físico) ou ferramentas como WAL-G/Barman para PITR (point-in-time recovery). Teste a restauração periodicamente; backup que não foi testado é apenas esperança.

+Vale a pena aprender SQL antes do ORM?

Sim. ORM abstrai o SQL, mas gera queries horríveis quando mal utilizado. Saber SQL permite otimizar o que o ORM gera, criar views e stored procedures quando faz sentido, e entender planos de execução. Aprenda SQL primeiro; depois use ORM como acelerador, não como substituto.

Fontes consultadas

Revisão editorial: publicado em . Última revisão em . Conteúdo educativo, sem patrocínio das ferramentas citadas.

Crédito da imagem: Ilustração editorial: Equipe Icardb

Leia também