In dbt you can define the same setting key in multiple places. Knowing where it lives and how it gets overridden is critical for both the Analytics Engineer exam and day-to-day work.
This article walks through the basics of overriding settings inside a model with the config() function, the precedence order, gotchas for incremental models, environment-based switching, when to use YAML vs. config(), and exam-prep angles — all from a practical perspective.
config() is the mechanism for declaring settings inside a model file via a Jinja block. Typical examples include materialized, schema, alias, tags, and incremental-related keys such as unique_key and on_schema_change.
When the same key is defined in multiple places, the general precedence is: low: dbt_project.yml → medium: resource properties YAML (the model's .yml) → high: in-model config(). Once you have this order in your head, you never have to guess where to override.
| Where | Example | Typical use | Precedence (low → high) |
|---|---|---|---|
| dbt_project.yml | models: my_project: +materialized: view | Project-wide or per-directory defaults | Low |
| Resource YAML (properties) | models: - name: fct_orders config: materialized: table | Declarative per-model management | Medium |
| In-model config() | {{ config(materialized='incremental', schema='mart') }} | Urgent one-off overrides or conditional logic | High |
Override precedence (higher = stronger)
Minimal config() sample
{{ config(
materialized='table',
schema='mart',
alias='orders_daily'
) }}
select * from {{ ref('stg_orders') }}The settings you'll override via config() most often are materialized, schema, alias, tags, and persist_docs (persisting column descriptions). Choose them with documentation and operations in mind.
schema and alias in particular tie directly into naming conventions and operational flow. Stick to project defaults and override only exceptional models in-model — that's the realistic approach.
Example with common config() keys
{{ config(
materialized='view',
schema='sandbox',
alias='vw_customer_latest',
tags=['customer', 'sla_daily'],
persist_docs={'relation': true, 'columns': true}
) }}
select * from {{ ref('stg_customer') }}For incremental models, in addition to materialized='incremental', you set unique_key, the strategy, on_schema_change, and so on appropriately. Warehouse-specific strategy options (e.g., merge, insert_overwrite) depend on what each warehouse supports.
unique_key defines row identity, and with the merge strategy, controlling which columns are updated (e.g., merge_update_columns) and how schema changes are handled (on_schema_change) really matters. On the exam, the role of these keys and the override precedence come up often.
Template config() for incremental models
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge',
on_schema_change='append_new_columns'
) }}
with src as (
select * from {{ ref('stg_orders') }}
)
select * from src
{% if is_incremental() %}
-- Example: only the last 90 days
where order_date >= dateadd(day, -90, current_date)
{% endif %}Conditional logic with target.name and var() is the go-to pattern in practice for switching schemas or materialization strategies between dev, staging, and production. Since Jinja is evaluated at parse time, keep the conditions self-contained in the config() block at the top of the model.
Excessive branching hurts readability. Set project defaults in dbt_project.yml and override only the exceptions in config() — that's the easiest pattern to maintain.
Example of environment-based switching
{% set is_heavy = var('heavy_model', false) %}
{% if target.name == 'prod' %}
{{ config(schema='mart', materialized= 'table' if is_heavy else 'view') }}
{% else %}
{{ config(schema=target.name ~ '_mart', materialized='view') }}
{% endif %}
select * from {{ ref('stg_heavy_fact') }}Settings that change rarely and are descriptive in nature (e.g., persist_docs, tags, owners, contracts) are clearer when pushed into YAML. On the other hand, temporary or exceptional overrides closely tied to execution strategy are easier to manage in-model with config().
Exam questions often ask "where should this go?" Organizing it as three layers — project defaults in dbt_project.yml, model-specific static metadata in YAML, environment/strategy exceptions in config() — gives you a stable answer pattern.
Side-by-side YAML and config() example
# models/schema.yml
models:
- name: fct_orders
description: Orders fact table.
config:
tags: ['finance']
-- models/fct_orders.sql
{{ config(materialized='incremental', unique_key='order_id') }}
select * from {{ ref('stg_orders') }}The Analytics Engineer exam asks about precedence rules, required keys for incremental models, the meaning of on_schema_change, and where config() should be placed. Warehouse-specific options, when they appear, are usually framed so that general knowledge is enough.
In practice, what works is making project defaults strong, keeping exceptions in the model, and using PR review to surface intent (why the override is needed) via comments.
The minimal-override pattern that wins code reviews
{{ config(materialized='incremental', unique_key='id') }}
-- Let dbt_project.yml/YAML own defaults like schema/alias/tags,
-- and override only the keys directly tied to strategy here
select * from {{ ref('stg_dim_user') }}Analytics Engineer
問題 1
Your organization's default is that everything under models is a view, but you want one specific model to be a table. Which approach overrides it at the highest precedence with the smallest blast radius?
正解: A
Precedence is dbt_project.yml < resource YAML < in-model config(). To override at the highest precedence with the smallest blast radius, the correct answer is to set materialized='table' in the in-model config(). --full-refresh is runtime recreate behavior, not a persistent materialization-type setting.
Where in the model file should config() be placed?
Because it is resolved during Jinja evaluation, the recommended placement is at the very top of the model SQL as a top-level Jinja block. Putting it later hurts readability and increases the risk of accidentally introducing conditional branches.
If the same key is set in both config() and YAML, which one wins?
The usual precedence is dbt_project.yml < resource YAML < in-model config(). When the same key is defined in both, the in-model config() takes effect.
Is unique_key always required for incremental models?
It depends on the strategy and warehouse. With the merge strategy, you typically specify unique_key to match rows for updates. Check the target warehouse documentation and the official dbt documentation for what is supported.
Practice with certification-focused question sets
Try free practice questionsNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
dbt Models: SQL-Defined Transformation Units (2026)
Model fundamentals — SELECT-based definitions, naming, refs,...
dbt Analytics Engineering Exam: Complete Guide (2026)
Pass the AE Certification — scope, weighting, sample questio...
dbt Cloud vs dbt Core: Feature & Cost Comparison (2026)
Honest comparison of dbt Cloud vs. dbt Core — IDE, scheduler...
dbt Project Structure: models/seeds/macros Layout (2026)
Recommended dbt project layout — models, seeds, macros, snap...
dbt_project.yml Explained: Every Config (2026)
Every dbt_project.yml setting that matters — paths, vars, ma...