Pular para o conteúdo

Além da ementa · tema 19 de 30

SQL para engenharia de dados

40 min · 2 vídeos · 13 cards · 1 drill

Por que cai

SQL é o único tema em que a banca pode pedir para você escrever ou descrever a consulta na hora. É teste de fluência: quem hesita entre ROW_NUMBER e GROUP BY entrega que usa SQL só para conferir resultado, não para transformar dado.

Pré-teste · 1 de 2

responda antes de ver

Você quer as contas que NÃO tiveram transação em janeiro. A tabela de transações permite id_conta nulo. Qual construção é mais segura?

Confiança:

Vídeo · português · 31 min

Mostra a janela sendo montada pedaço por pedaço: PARTITION BY, ORDER BY e o efeito de cada um na saída. Pare o vídeo antes de cada resultado e tente prever a saída — é assim que a fluência se instala.

Em uma frase

O SQL que a sabatina cobra é o de transformação: anti-join, CTE, window function, leitura de plano — e saber que o SQL analítico sobre arquivos joga com regras diferentes do SQL transacional sobre índice.

Join, anti-join e o perigo do NOT IN

Anti-join é "traga o que não tem correspondência". Duas formas seguras e uma armadilha:

-- Forma canônica: NOT EXISTS
SELECT c.id_conta
FROM contas c
WHERE NOT EXISTS (
  SELECT 1 FROM transacoes t WHERE t.id_conta = c.id_conta
);

-- Equivalente: LEFT JOIN com IS NULL
SELECT c.id_conta
FROM contas c
LEFT JOIN transacoes t ON t.id_conta = c.id_conta
WHERE t.id_transacao IS NULL;

Agregação e HAVING

WHERE filtra linha antes da agregação. HAVING filtra grupo depois dela. "Clientes com mais de 50 transações no mês" precisa de HAVING, porque a contagem só existe depois do GROUP BY.

CTE no lugar de subquery aninhada

CTE nomeia o passo. Três subqueries aninhadas obrigam a leitura de dentro para fora; a mesma lógica em CTE se lê na ordem em que foi pensada, e cada bloco pode ser executado isolado durante a depuração. Em consulta de pipeline, isso vale mais do que qualquer ganho de performance — o otimizador costuma produzir o mesmo plano.

Window functions: o coração do tema

window functionFunção que calcula um valor por linha olhando um conjunto de linhas relacionadas (a janela), sem colapsar o resultado como o GROUP BY faz.
-- Deduplicar / última transação por conta
WITH ordenadas AS (
  SELECT t.*,
         ROW_NUMBER() OVER (
           PARTITION BY id_conta
           ORDER BY data_transacao DESC, id_transacao DESC
         ) AS rn
  FROM transacoes t
)
SELECT * FROM ordenadas WHERE rn = 1;

-- Variação em relação à transação anterior
SELECT id_conta, data_transacao, valor,
       valor - LAG(valor) OVER (PARTITION BY id_conta ORDER BY data_transacao) AS variacao
FROM transacoes;

-- Gasto acumulado no mês
SELECT id_conta, data_transacao, valor,
       SUM(valor) OVER (
         PARTITION BY id_conta ORDER BY data_transacao
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS acumulado
FROM transacoes;
FunçãoEm caso de empateUso típico
ROW_NUMBERDesempata arbitrariamente, nunca repeteDeduplicar, pegar a linha mais recente
RANKRepete o número e pula as posições seguintesRanking em que o buraco é informativo
DENSE_RANKRepete o número e não pulaTop N por categoria mantendo empates
-- Mesma dedupe, com QUALIFY
SELECT *
FROM transacoes
QUALIFY ROW_NUMBER() OVER (PARTITION BY id_transacao ORDER BY data_ingestao DESC) = 1;

Ler o plano de execução

EXPLAIN mostra o que o motor pretende fazer. O que procurar, em ordem:

  1. 1Varredura completa onde deveria haver poda de partição — quase sempre é filtro com função aplicada sobre a coluna particionada.
  2. 2Estratégia de join escolhida: broadcast, sort-merge ou hash, e se ela faz sentido para o tamanho dos lados.
  3. 3Estimativa de linhas muito distante do real, sinal de estatística desatualizada.
  4. 4Ordenação ou agregação que você não pediu, geralmente induzida por DISTINCT ou por window mal escrita.
  5. 5Ordem dos joins: filtrar cedo e juntar tarde costuma valer mais que qualquer hint.

Índice: quando ajuda e quando atrapalha

Ajuda em consulta seletiva por chave — cadastro por CPF, transação por id. Atrapalha em tabela com escrita intensa, porque cada INSERT mantém a estrutura, e quando a consulta lê boa parte da tabela, caso em que a varredura sequencial sai mais barata que o acesso aleatório.

SQL analítico não é SQL transacional

AspectoOLTP (Postgres, core bancário)Analítico (Spark SQL, Athena)
Unidade de acessoLinha, via índiceArquivo e coluna, via varredura podada
Otimização principalÍndice e estatística de colunaParticionamento, ordenação, poda por estatística do Parquet
Custo dominanteBusca aleatória em discoLeitura de dados e shuffle na rede
AtualizaçãoUPDATE e DELETE por linha, transacionalReescrita de arquivo; MERGE só com formato de tabela como Delta
TransaçãoACID multi-tabelaACID por tabela, quando há Delta ou Iceberg

Dizer isso em voz alta muda o tom da resposta: você não está usando "o mesmo SQL num lugar maior", está usando uma linguagem parecida sobre um motor com outra física de custo.

Se quiser outro ângulo

Curto e direto na troca de subquery aninhada por CTE. Assista pensando em como você justificaria essa troca em voz alta para um revisor de código.

Como cai na sabatina

Como você pegaria a última transação de cada conta?

Erros comuns

  • Usar NOT IN com subconsulta que pode retornar NULL. Um único NULL faz a condição virar desconhecida e o resultado volta vazio. NOT EXISTS não tem esse problema.
  • Achar que GROUP BY resolve 'a última transação de cada conta'. GROUP BY colapsa a linha e você perde as outras colunas; o certo é ROW_NUMBER particionado pela conta.
  • Filtrar window function no WHERE. A janela é avaliada depois do WHERE; ou você envolve numa CTE e filtra fora, ou usa QUALIFY onde o motor suporta.
  • Confundir RANK com DENSE_RANK e com ROW_NUMBER. Em empate, ROW_NUMBER desempata arbitrariamente, RANK pula posições e DENSE_RANK não pula.
  • Pedir índice para tabela analítica em Parquet. Em Spark SQL e Athena não existe índice como no OLTP; o que ganha é particionamento, ordenação de arquivos e poda por estatística.
  • Empilhar subquery aninhada onde uma CTE resolveria. Além de ilegível, dificulta reaproveitar o mesmo bloco e conversar sobre o plano com outra pessoa.

Flashcards

Card 1 de 13

0 certos · 0 a rever

Drill

Qual construção SQL você usaria?

Esquema fictício: transacoes(id_transacao, id_conta, data_transacao, valor, estabelecimento, canal), contas(id_conta, id_cliente, tipo_conta, data_abertura), clientes(id_cliente, nome, cpf, segmento). Para cada requisito, diga em voz alta qual construção resolve, antes de conferir.

  1. 1.A última transação de cada conta, com valor e estabelecimento.

  2. 2.Contas que nunca transacionaram.

  3. 3.Clientes com mais de 50 transações no mês.

  4. 4.Variação de valor entre cada transação e a anterior da mesma conta.

  5. 5.Saldo acumulado de gastos por conta ao longo do mês.

  6. 6.As três maiores transações de cada segmento de cliente, mantendo empates na terceira posição.

  7. 7.Remover transações duplicadas pela mesma chave de autorização, mantendo a de ingestão mais recente, num motor que suporta a cláusula.

  8. 8.Total transacionado por conta, incluindo contas sem nenhuma transação, com zero no lugar de nulo.

Quiz · 1 de 2

Em qual situação DENSE_RANK é a escolha certa em vez de ROW_NUMBER?

Explique para um gerente

Explique para um analista de negócio, sem escrever código, o que uma window function faz de diferente de um GROUP BY.

Responda em voz alta antes de seguir. Se travar numa palavra técnica, é sinal de que ainda não entendeu essa parte.

Perguntas de sabatina deste tema

  • · Como você pegaria a última transação de cada conta? Descreva a consulta e diga por que não usa GROUP BY.
  • · Uma consulta que roda em 2 segundos no ambiente de desenvolvimento leva 8 minutos em produção sobre a tabela real de transações. Como você investiga?
Responder no simulado

Para ir além