Modelos Incrementais
Um modelo incremental processa apenas as linhas que mudaram desde a última execução, em vez de reconstruir a tabela inteira do zero a cada vez.
O problema de reconstruir tudo
Por padrão, um modelo table no dbt se reconstrói completamente a cada execução: drop, re-select, re-insert de todas as linhas. Para uma dimensão de 10.000 linhas isso é tranquilo. Para uma tabela de eventos com dois bilhões de linhas, é uma query lenta e cara que você roda repetidamente para recalcular linhas que não mudaram desde ontem.
Um modelo incremental quebra esse padrão. Na primeira execução ele constrói a tabela inteira. Em todas as execuções seguintes, processa apenas as linhas novas ou alteradas e as incorpora na tabela que já existe.
Comece com a materialização table ou view a menos que tenha uma razão concreta para mudar. Rebuilds completos são mais simples, mais fáceis de debugar e garantem correção. Use incremental quando uma tabela é genuinamente grande demais e lenta demais para reconstruir.
Como funciona
Duas peças fazem um modelo incremental funcionar: uma config que marca o modelo como incremental, e um bloco is_incremental() que filtra apenas as linhas novas.
{{ config(materialized='incremental', unique_key='order_id') }}
select * from {{ source('shop', 'orders') }}
{% if is_incremental() %}
where updated_at > (select max(updated_at) from {{ this }})
{% endif %}
is_incremental() retorna true somente quando as três condições se cumprem: a tabela de destino já existe, o modelo está materializado como incremental e --full-refresh não foi passado. Quando é true, a cláusula where está ativa e apenas as linhas novas chegam à lógica incremental.
{{ this }} é uma variável Jinja do dbt que resolve para o nome completo da tabela de destino do próprio modelo, por exemplo analytics.fct_orders. O dbt a substitui em tempo de compilação, então o sub-select sempre escaneia a tabela de destino ativa, e não uma source.
As cinco estratégias
A incremental_strategy diz ao dbt o que fazer com as linhas que passam pelo filtro is_incremental(). Escolha com base no seu warehouse, no formato dos dados e nos requisitos de correção.
Full refresh e primeira execução
Quando is_incremental() é false: na primeira execução de um modelo, ou quando você passa --full-refresh, o dbt ignora toda a lógica incremental e reconstrói a tabela inteira do zero. Cada widget de estratégia abaixo tem um toggle --full-refresh para revisitar isso a qualquer momento.
append
Insere cada linha recebida sem verificar duplicatas.
{{ config(
materialized='incremental',
incremental_strategy='append'
) }}
Rápido e barato. Funciona para tabelas de eventos onde o filtro is_incremental() garante linhas sem sobreposição, ou onde os modelos downstream deduplicam. Se o modelo rodar novamente e o filtro não for restrito o suficiente, você terá duplicatas; avance pelo widget abaixo e veja o pedido 3 ser inserido duas vezes.
merge
Compara as linhas recebidas com a tabela existente pela unique_key. Linhas que batem são atualizadas no lugar; linhas que não batem são inseridas. Estratégia padrão no Snowflake, BigQuery, Postgres e Redshift quando unique_key está configurado.
{{ config(
materialized='incremental',
unique_key='order_id'
) }}
A estratégia mais amplamente útil. Avance pelo widget abaixo, depois desative unique_key configured para ver o que acontece sem ele; o pedido 3 é duplicado em vez de atualizado, o erro clássico de incremental.
delete+insert
Apaga cada linha existente cuja chave aparece no lote recebido e depois insere o lote completo. Produz a mesma tabela final que o merge, mas usa DELETE + INSERT em vez de um MERGE statement. O widget mostra os dois SQL statements e a tag “replaced” no resultado; mesmo estado final que o merge, mecânica diferente.
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='delete+insert'
) }}
insert_overwrite
Substitui partições inteiras em vez de linhas individuais. Sem unique_key; a granularidade é a partição, não a linha. Suportado no Snowflake, Databricks, Spark, Redshift e Postgres. Não suportado no BigQuery.
{{ config(
materialized='incremental',
incremental_strategy='insert_overwrite',
partition_by={'field': 'created_date', 'data_type': 'date'}
) }}
O risco principal: se o lote de uma partição não contiver todas as linhas que existiam antes, as linhas ausentes são perdidas definitivamente. No exemplo abaixo, e6 e e7 existiam na partição de Jan 15 mas não estavam no lote; elas são perdidas permanentemente após o overwrite.
microbatch
Divide a execução em uma query delimitada por período de tempo (hora, dia, mês). Cada batch é independente, então o dbt pode rodá-los em paralelo e dar retry apenas nos que falharam. Feito para tabelas grandes de série temporal onde uma única query incremental é lenta demais ou propensa a erros demais.
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='created_at',
batch_size='day'
) }}
Suportado no Snowflake, Databricks, Spark, Trino e Athena. Ainda não disponível no Postgres, BigQuery ou Redshift.
Config avançado
on_schema_change
Controla o que acontece quando você adiciona, remove ou altera colunas no SQL do modelo. O padrão ignore descarta silenciosamente as novas colunas; é uma fonte frequente de schema drift que só aparece mais tarde.
No widget abaixo, o SQL do modelo muda de [order_id, amount, legacy_field] para [order_id, amount, status]. Escolha cada opção para ver como a tabela de destino fica após a próxima execução incremental.
merge_update_columns / merge_exclude_columns
Restringe quais colunas são atualizadas durante um merge. Útil quando algumas colunas são definidas uma vez na inserção e nunca devem ser sobrescritas; por exemplo, created_at.
{{ config(
materialized='incremental',
unique_key='order_id',
merge_update_columns=['status', 'updated_at']
) }}
incremental_predicates
Limita o scan da tabela de destino existente durante o merge, reduzindo o custo em tabelas grandes. Use DBT_INTERNAL_DEST para referenciar o destino e DBT_INTERNAL_SOURCE para o novo lote.
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_predicates=["DBT_INTERNAL_DEST.created_date >= dateadd('day', -7, current_date)"]
) }}
Isso diz ao dbt: procure linhas correspondentes apenas nos últimos 7 dias da tabela de destino, não em todo o histórico. Certifique-se de que o filtro is_incremental() produz linhas apenas dentro da janela do predicate; caso contrário, as linhas existentes fora dela não serão atualizadas.
Use o widget abaixo para comparar as três estratégias por linha lado a lado. Todos os controles são ao vivo.
Quando usar este padrão
Use um modelo incremental quando uma tabela é grande e majoritariamente append (event logs, clickstream, orders) e um rebuild completo ficou lento ou caro demais. Para tabelas pequenas, um modelo table simples é mais direto e as partes móveis extras não valem a pena.