Transformação

Tutorial dbt: SQL versionado, testado e documentado

Com dbt você para de manter scripts SQL soltos e passa a manter um projeto: dependências explícitas, testes automáticos, ambientes separados e documentação gerada. Abaixo, o projeto mínimo que já tem qualidade de produção.

Estrutura de projeto que os times usam

meu_projeto/
├── dbt_project.yml
├── models/
│   ├── staging/          # 1 model por tabela de origem, só limpeza
│   │   ├── stg_pedidos.sql
│   │   ├── stg_clientes.sql
│   │   └── _staging.yml   # sources + testes
│   ├── intermediate/     # joins e regras reutilizáveis
│   │   └── int_pedidos_enriquecidos.sql
│   └── marts/            # o que o negócio consome
│       ├── fct_pedidos.sql
│       └── dim_clientes.sql
├── macros/
├── snapshots/
└── tests/

Regra de ouro: staging não faz join, marts não fazem limpeza. Isso sozinho evita metade da bagunça em projetos dbt.

Seu primeiro model com ref()

-- models/staging/stg_pedidos.sql
{{ config(materialized='view') }}

select
    id                        as pedido_id,
    cliente_id,
    lower(trim(status))       as status,
    valor_centavos / 100.0    as valor,
    cast(criado_em as date)   as data_pedido
from {{ source('loja', 'pedidos') }}
where criado_em is not null
-- models/marts/fct_pedidos.sql
{{ config(materialized='table') }}

with pedidos as (
    select * from {{ ref('stg_pedidos') }}
),
clientes as (
    select * from {{ ref('stg_clientes') }}
)

select
    p.pedido_id,
    p.data_pedido,
    p.valor,
    c.uf,
    c.segmento
from pedidos p
left join clientes c on c.cliente_id = p.cliente_id
where p.status = 'pago'

Você nunca escreve o nome físico da tabela. O ref() resolve o schema certo em dev e em prod e monta o grafo de execução sozinho.

Testes: a parte que gera confiança

# models/staging/_staging.yml
version: 2

sources:
  - name: loja
    schema: raw
    tables:
      - name: pedidos
      - name: clientes

models:
  - name: stg_pedidos
    columns:
      - name: pedido_id
        tests: [unique, not_null]
      - name: cliente_id
        tests:
          - not_null
          - relationships:
              to: ref('stg_clientes')
              field: cliente_id
      - name: status
        tests:
          - accepted_values:
              values: ['pago', 'pendente', 'cancelado']
dbt build          # roda models + testes na ordem do grafo
dbt run --select marts+   # só marts e o que depende deles
dbt test --select stg_pedidos
dbt docs generate && dbt docs serve

Model incremental para tabelas grandes

{{ config(
    materialized='incremental',
    unique_key='pedido_id',
    incremental_strategy='merge'
) }}

select *
from {{ ref('stg_pedidos') }}

{% if is_incremental() %}
  where data_pedido >= (select coalesce(max(data_pedido), '1900-01-01') from {{ this }})
{% endif %}

Na primeira execução o dbt cria a tabela inteira; nas seguintes, processa só o novo e faz MERGE pela unique_key. É o que derruba custo de warehouse de verdade.

Erros comuns em projetos dbt no Brasil

  • Usar nome de tabela cru em vez de ref() — quebra o grafo e o ambiente de dev.
  • Model de 400 linhas fazendo tudo. Quebre em staging → intermediate → mart.
  • Zero testes: sem unique e not_null nas chaves, duplicata chega no dashboard.
  • Tudo como table: custo alto sem necessidade. Comece com view.
  • Regra de negócio escondida no BI em vez de estar no mart, versionada no Git.

Perguntas frequentes

O que é dbt?
dbt (data build tool) é a camada de transformação do stack moderno: você escreve SELECTs em SQL e o dbt cria as tabelas e views no seu warehouse, na ordem correta, com testes, documentação e versionamento em Git.
dbt substitui o Airflow?
Não. O dbt transforma dados dentro do warehouse; o Airflow orquestra quando o dbt roda e o que roda antes e depois. Times maduros usam os dois juntos.
dbt Core ou dbt Cloud?
dbt Core é open source e roda via CLI, geralmente dentro do Airflow ou de um container. dbt Cloud adiciona IDE, agendador e permissões gerenciadas. Para aprender e para a maioria dos times pequenos, Core basta.
O que faz a função ref() no dbt?
ref('outro_model') referencia outro model e, com isso, o dbt descobre o grafo de dependências automaticamente e resolve o nome real da tabela no ambiente correto (dev, staging, prod).
Quais materializações existem?
view (padrão), table, incremental e ephemeral. Use view no staging, table quando a leitura é frequente, incremental para tabelas grandes de fatos e ephemeral para CTEs reutilizáveis que não precisam existir no banco.
dbt funciona com BigQuery, Snowflake e Databricks?
Sim, além de Redshift, Postgres, DuckDB e Fabric. Cada um tem um adapter próprio; o SQL muda pouco e o restante do projeto é igual.

Pronto para assinar?

7 dias grátis. Depois, menos que um café por mês — cancele quando quiser.

Assinar — 7 dias grátis