SQL e Modelagem de Dados que Escalam Além do Primeiro Painel de Controle
Turn this article into takeaways for your work.
Each assistant summarizes the article only for you and suggests best practices for your work.
Já herdei três data warehouses assim e o diagnóstico é sempre o mesmo. Existe uma consulta que alguém colou numa mensagem direta no Slack às 16h de uma sexta. O marketing construiu um painel de controle em cima dela. Vendas construiu outro painel em cima desse. Seis meses depois, o CFO abre os dois na mesma reunião, vê dois números de receita diferentes e para de confiar na equipe de dados pelo resto do trimestre.
A solução não é um SQL mais inteligente. É disciplina: camadas, testes, nomenclatura e uma cadência de descontinuação. Este guia é essa disciplina, escrita para analistas que já sabem fazer JOIN e querem parar de estar de plantão para "por que esse painel mostra algo diferente".
A Consulta da Thread do Slack
É assim que começa. Alguém no #revops faz uma pergunta. Você escreve uma consulta de 60 linhas contra a réplica de produção, solta a resposta na thread e segue em frente. Essa consulta é copiada para um notebook do Mode. O notebook do Mode vira um tile no Looker. O tile do Looker vira um gráfico no board deck.
Noventa dias depois o esquema subjacente muda, a consulta quebra silenciosamente e ninguém percebe porque o gráfico ainda renderiza, só que com números errados. Ou pior: a consulta nunca quebrou, mas nunca teve uma definição nomeada de "cliente ativo" e agora três equipes estão olhando para três contagens diferentes.
A consulta da thread do Slack está ótima como consulta de thread do Slack. O problema é o caminho de "lookup" para "painel de controle estrutural" sem nenhum ponto de verificação no meio. Você precisa de um ponto de verificação. O ponto de verificação é um modelo.
Duas Definições de Receita é um Evento de Confiança
No dia em que finanças e produto aparecem na mesma reunião com dois números de receita diferentes, você perdeu um ano de confiança e vai passar seis meses tentando recuperá-la. Já vi isso acontecer em três empresas diferentes. Não é um problema de ferramental. O Snowflake não te falhou. O dbt não te falhou. O que falhou é que ninguém definiu o que "receita" significa antes de cinco pessoas escreverem cinco consultas.
Isso é prevenível com engenharia. Camadas e testes previnem o problema das duas receitas. Nomenclatura e descontinuação evitam que o data warehouse deteriore. O trabalho é tedioso de propósito. Se seu stack de dados parece emocionante no dia a dia, provavelmente algo está em chamas.
Práticas de SQL que Realmente Escalam
Quatro hábitos separam analistas que conseguem passar o data warehouse para outra pessoa de analistas cujo código morre no momento em que saem.
CTEs em vez de subconsultas aninhadas. Uma subconsulta aninhada é um artefato de escrita única. Você consegue escrevê-la, mas seis semanas depois não consegue lê-la, e a próxima pessoa também não. CTEs permitem que você nomeie cada etapa, depure cada etapa e raciocine sobre um modelo mental por bloco.
-- Não faça isso
SELECT customer_id, SUM(amount)
FROM (
SELECT * FROM orders
WHERE status = 'completed'
AND created_at >= (SELECT MIN(created_at) FROM orders WHERE ...)
) o
JOIN (SELECT * FROM customers WHERE deleted_at IS NULL) c
ON o.customer_id = c.id
GROUP BY 1;
-- Faça isso
WITH completed_orders AS (
SELECT *
FROM {{ ref('stg_shopify__orders') }}
WHERE status = 'completed'
),
active_customers AS (
SELECT *
FROM {{ ref('stg_shopify__customers') }}
WHERE deleted_at IS NULL
),
revenue_by_customer AS (
SELECT
c.customer_id,
SUM(o.amount) AS lifetime_revenue
FROM active_customers c
LEFT JOIN completed_orders o
ON o.customer_id = c.customer_id
GROUP BY c.customer_id
)
SELECT * FROM revenue_by_customer;
A segunda versão é mais longa. Também é depurável. Você pode selecionar qualquer CTE, rodar isoladamente e verificar a contagem de linhas. Esse é o jogo inteiro.
Testes nomeados em todo modelo. Não "vou adicionar testes depois". Em todo modelo, no mesmo PR. Os quatro que valem o esforço:
# models/marts/_marts.yml
version: 2
models:
- name: dim_customers
columns:
- name: customer_id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['active', 'churned', 'paused']
- name: account_owner_id
tests:
- relationships:
to: ref('dim_users')
field: user_id
Quatro testes, quinze linhas de YAML e você vai capturar noventa por cento das quebras silenciosas que de outra forma apareceriam num board deck. unique e not_null em toda chave primária. accepted_values em toda coluna com formato de enum. relationships em toda chave estrangeira. Se você não testa as chaves, não tem um modelo. Tem uma consulta.
Um único modelo de fonte da verdade por entidade de negócio. Um dim_customers. Um fct_orders. Um dim_users. Não dim_customers_v2, não dim_customers_marketing, não dim_customers_para_financas_q3. Se marketing e finanças precisam de cortes diferentes de clientes, eles recebem colunas diferentes no mesmo dim_customers, ou constroem marts downstream em cima dele. Eles não têm o direito de clonar a tabela.
O clone é a forma como você cria o problema das duas receitas. Elimine-o ao primeiro sinal.
Funções de janela em vez de self-joins. Se você está fazendo um self-join em uma tabela contra si mesma para calcular "data do pedido anterior" ou "ranking dentro do cliente", você está trabalhando demais. LAG(), LEAD(), ROW_NUMBER() e RANK() fazem o mesmo trabalho em uma única passagem com metade das linhas e um décimo do custo de processamento.
Camadas de Modelagem de Dados: Staging, Intermediate, Marts
Esta é a parte que ninguém quer fazer porque parece trabalho extra. Não é trabalho extra. É o trabalho que torna tudo o que vem depois possível.
Staging (stg_). Um modelo por tabela de origem. Renomeie colunas para a convenção do seu projeto. Converta tipos. Nada mais. Sem joins, sem lógica de negócio, sem agregações. O trabalho da camada de staging é dar a cada modelo downstream uma visão limpa, previsível e nomeada de uma tabela de origem. Se você pular o staging porque "é só uma renomeação", vai se arrepender no dia em que o Stripe renomear uma coluna ou o sync do Fivetran mudar a capitalização.
-- models/staging/stripe/stg_stripe__charges.sql
WITH source AS (
SELECT * FROM {{ source('stripe', 'charges') }}
),
renamed AS (
SELECT
id AS charge_id,
customer AS customer_id,
amount / 100.0 AS amount_usd,
status AS charge_status,
created::TIMESTAMP AS charged_at
FROM source
)
SELECT * FROM renamed;
Esse é o modelo inteiro. É tedioso. Esse é o ponto.
Intermediate (int_). Aqui vivem os joins, a lógica de negócio e os blocos reutilizáveis. Modelos intermediate nunca são expostos ao BI. Eles existem para manter seus modelos de mart legíveis e permitir a reutilização de peças. Se três marts precisam de "MRR mensal por cliente", isso é um modelo int_customer_mrr_monthly, construído uma vez e referenciado três vezes.
-- models/intermediate/int_customer_mrr_monthly.sql
WITH subscriptions AS (
SELECT * FROM {{ ref('stg_stripe__subscriptions') }}
),
months AS (
SELECT * FROM {{ ref('int_calendar_months') }}
),
active_in_month AS (
SELECT
m.month_start,
s.customer_id,
s.monthly_amount_usd
FROM months m
LEFT JOIN subscriptions s
ON m.month_start BETWEEN s.started_at AND COALESCE(s.canceled_at, '9999-12-31')
)
SELECT
customer_id,
month_start,
SUM(monthly_amount_usd) AS mrr_usd
FROM active_in_month
GROUP BY 1, 2;
Marts (fct_ / dim_). É o que as ferramentas de BI consultam. Desnormalizado, legível para o negócio e nomeado da forma como um executivo nomearia. fct_ para tabelas de fatos (pedidos, cobranças, sessões). dim_ para tabelas de dimensões (clientes, produtos, funcionários). Sua camada de marts é a API do seu data warehouse. Trate mudanças nela como mudanças de API: com versão, comunicadas e descontinuadas com aviso prévio.
Uma equipe de dados de dez pessoas geralmente acaba com entre 200 e 400 modelos distribuídos nessas três camadas. Aproximadamente 50% a 60% staging, 25% a 30% intermediate, 15% a 20% marts. Se a contagem de marts crescer além dessa proporção, você está colocando lógica de negócio nos marts que deveria estar no intermediate.
O Diagnóstico das "Duas Definições de Receita"
Você consegue rodar isso em vinte minutos. Faça antes de escrever um novo modelo num data warehouse que você herdou.
- Extraia todo tile de painel de controle, relatório de BI e consulta salva que tenha a palavra "receita" no título ou na coluna.
- Para cada um, copie o SQL em um único documento.
- Compare as cláusulas WHERE, as condições de JOIN e os alvos de SUM.
- Faça uma tabela: nome do painel, responsável, definição, origem.
- Leve para finanças. Obtenha uma única definição por escrito.
- Construa um único modelo
fct_revenueque corresponda à definição acordada. - Migre todos os painéis de controle para esse modelo. Descontinue as consultas antigas.
A maioria das equipes encontra de três a cinco definições de receita diferentes na primeira auditoria. Às vezes todas estão "certas" para algum propósito específico, mas apenas uma delas é a que o CFO vai defender numa reunião com investidores. Essa é a que vive no modelo. As outras são reescritas como views em cima dele (vw_revenue_marketing_attributed, vw_revenue_booking_basis) para que fique claro que são cortes do mesmo número, não números separados.
Rode a mesma auditoria para "cliente ativo", "churn", "ARR" e "MRR". Você vai encontrar o mesmo problema. Corrija da mesma forma.
Convenções de Nomenclatura do dbt
Nomenclatura não é detalhe estético. É como o próximo analista se orienta no seu data warehouse sem precisar te perguntar nada.
stg_{source}__{entity}. Sublinhado duplo separa origem de entidade.stg_stripe__charges,stg_hubspot__contacts,stg_shopify__orders. O sublinhado duplo é a indicação visual de que o lado esquerdo é o nome de um sistema.int_{entity}_{verb}. Descreve o que o modelo faz.int_orders_pivoted,int_customers_enriched,int_sessions_attributed.fct_{event}edim_{entity}. Exemplos:fct_orders,fct_charges,fct_page_views,dim_customers,dim_products,dim_dates.- Aliases de origem em testes, não aliases de coluna no SQL de mart. Seu
dim_customers.customer_iddeve sercustomer_id, nãodim_customers__customer_id.
O sufixo _v2 é um sinal de alerta. Se você vê dim_customers_v2.sql ao lado de dim_customers.sql, alguém ficou com medo de quebrar o original e criou um fork. Versionamento pertence ao Git, não ao nome do arquivo. A ação correta é: reescreva dim_customers, entregue a mudança com uma nota de migração, descontinue o comportamento antigo com uma data no calendário. Manter os dois para sempre garante que o problema das duas receitas vai voltar usando um chapéu diferente.
Disciplina de Controle de Versão e Changelog
Sem merges diretos para a branch principal. Nunca. Mesmo para a correção de uma linha. No momento em que você abre uma exceção, ela se torna a regra.
Todo PR tem uma linha de entrada no CHANGELOG.md:
## 2026-04-29
- `fct_orders`: adicionada coluna `is_first_order`. Afeta: marketing-attribution, lifecycle-emails dashboards. (@camellia)
- `breaking: dim_customers`: renomeado `acct_owner` para `account_owner_id`. Afeta: todos os painéis CRM. Migração: ver #1284. (@camellia)
- `stg_stripe__charges`: tratamento de `customer_id` nulo em checkouts de convidados. (@camellia)
Três regras para o changelog:
- Todo PR adiciona uma entrada. Nenhum PR é entregue sem uma. O CI pode reforçar isso.
- Mudanças de esquema recebem a tag
breaking:e um aviso no#data-changesantes do merge do PR. - O campo "afeta" nomeia painéis de controle ou modelos downstream, não frases vagas como "vários relatórios". Se você não consegue nomear o que afeta, não conhece seu grafo downstream bem o suficiente para fazer o merge.
Mudanças de esquema também recebem uma descontinuação com data no calendário. A coluna antiga fica por 30 dias com um comentário, a nova coluna é adicionada em paralelo, os consumidores downstream migram, depois a coluna antiga é removida. Sem exceções para renomeações "simples". Renomeações simples são como você descobre que alguém construiu um fluxo no Zapier em cima do seu data warehouse e ninguém te avisou.
Painel de Controle vs. Análise Pontual: Quando Construir o Quê
Regra de decisão: esse número vai ser solicitado novamente em 30 dias?
- Sim → é um painel de controle. O número vive atrás de um modelo. O painel tem um responsável.
- Não → envie o CSV. Inclua o link do SQL na thread da solicitação ou no PR. Siga em frente.
O erro é construir um painel de controle para cada solicitação pontual. Seis meses depois você tem 400 painéis, 350 deles não foram abertos desde o dia em que foram entregues e o analista que os construiu saiu da empresa.
Painéis de controle têm um campo de responsável. Não "a equipe de dados". Um nome. Se um painel de controle existe sem um responsável nomeado, arquive-o. O trabalho do responsável é responder "isso ainda está certo?" quando partes interessadas perguntam. Se ninguém é responsável, ninguém pode responder e você voltou ao problema de confiança.
SQL de análise pontual vai em uma pasta analyses/ no seu projeto dbt, commitada e revisada, mas sem criar um modelo. Isso te dá um histórico documentado sem inflar o data warehouse.
Cadência de Descontinuação
Data warehouses deterioram como sótãos deterioram: devagar, depois tudo de uma vez. A cadência que previne isso:
- Revisão trimestral de painéis de controle por data da última visualização. A maioria das ferramentas de BI expõe isso. Extraia uma lista a cada trimestre.
- 60 dias sem visualização → banner de descontinuação. Uma mensagem visível de "este painel está programado para arquivamento em AAAA-MM-DD". Notifica o responsável. Força uma decisão.
- 90 dias sem visualização → arquivado. Mova para uma pasta
archive/. Marque o modelo subjacente comoenabled: falsena configuração do dbt para que pare de ser construído.
# models/marts/legacy/_legacy.yml
models:
- name: fct_orders_2024_legacy
config:
enabled: false
description: |
Arquivado em 2026-04-29, substituído por fct_orders.
Última consulta em 2026-01-12. Responsável saiu da empresa em Q4-2025.
Após um ano nessa cadência, você vai reduzir a contagem de modelos em 30% a 40% sem nenhuma reclamação downstream. Os modelos que você eliminou já estavam mortos. Você só está tornando isso oficial para que parem de gerar custo de processamento e confundir novos analistas.
Armadilhas Comuns
As quatro que vejo com mais frequência, classificadas pelo dano que causam:
- Modelar antes de acordar as definições. Faça a auditoria. Obtenha a definição. Depois construa o modelo. A outra ordem produz o problema das duas receitas num data warehouse novo.
- Pular o staging porque "é só uma renomeação". Pular o staging significa que todo modelo downstream quebra no dia em que o esquema de uma origem muda. O staging é o amortecedor. Pague o preço do tédio.
- Deixar arquivos
_v2se multiplicarem. Todo_v2é uma confissão de que alguém ficou com medo de quebrar o original. Corrija o original. Comunique a mudança. Entregue um modelo. - Nenhum responsável em modelos críticos. Um
fct_revenuesem responsável é um passivo com um arquivo SQL anexado. O campo de responsável é obrigatório no YAML do seu modelo, assim como os testes.
Como é "Pronto"
Você construiu essa disciplina quando:
- Toda métrica de negócio tem exatamente um modelo que a define.
- Todo modelo tem pelo menos os testes
uniqueenot_nullna sua chave primária. - Todo painel de controle tem um responsável nomeado.
- Um novo analista consegue rastrear qualquer número em qualquer painel de controle até a sua origem (através de marts, intermediate, staging, origem) em menos de dez minutos, sem perguntar a ninguém.
- O changelog diz o que mudou na última semana sem precisar abrir o Slack.
- Seu custo de processamento está estável ou caindo enquanto a contagem de modelos cresce, porque você descontinuou os modelos mortos.
Esse é o padrão. É tedioso. É também como você para de ser a equipe que recebe um chamado às 23h porque o board deck não fecha.
Saiba Mais

Principal Product Marketing Strategist
On this page
- A Consulta da Thread do Slack
- Duas Definições de Receita é um Evento de Confiança
- Práticas de SQL que Realmente Escalam
- Camadas de Modelagem de Dados: Staging, Intermediate, Marts
- O Diagnóstico das "Duas Definições de Receita"
- Convenções de Nomenclatura do dbt
- Disciplina de Controle de Versão e Changelog
- Painel de Controle vs. Análise Pontual: Quando Construir o Quê
- Cadência de Descontinuação
- Armadilhas Comuns
- Como é "Pronto"
- Saiba Mais