This is a lookup table, not a tutorial. For the when-to-use-what framework, see the materializations concepts page. For a worked example, see the incremental guide.

view

{{ config(
    materialized='view',
    tags=['staging'],
    docs={'node_color': '#3a6e96'}
) }}
OptionDefaultAccepted values
materializedrequiredview
tags[]list of strings
docs.node_colorNonehex color for dbt docs
aliasNoneoverride the database-side name
schemaprofile defaultstring
database (catalog)profile defaultstring

table

{{ config(
    materialized='table',
    file_format='delta',
    liquid_clustered_by=['customer_id', 'event_date'],
    tblproperties={'delta.autoOptimize.optimizeWrite': 'true'},
    databricks_compute='heavy'
) }}
OptionDefaultAccepted values
materializedrequiredtable
file_formatdeltadelta, parquet, hudi (delta preferred)
liquid_clustered_byNonelist of column names
auto_liquid_clusterFalsebool; Databricks picks keys
partition_byNonelist of columns; use only for hard isolation
tblproperties{}dict of Delta table properties
databricks_computedefaultname from profiles.yml databricks_compute map
location_rootNoneexternal location for managed tables
include_full_name_in_pathFalsebool; include catalog/schema in path

Warning

Never combine liquid_clustered_by with auto_liquid_cluster. Pick one. With both set, dbt raises a config error at parse time, but if it slipped through an older version you get non-deterministic clustering.

incremental

{{ config(
    materialized='incremental',
    incremental_strategy='merge',
    unique_key='order_id',
    on_schema_change='append_new_columns',
    incremental_predicates=["DBT_INTERNAL_DEST.updated_at >= current_date - interval 7 days"],
    liquid_clustered_by=['order_id'],
    merge_exclude_columns=['_dbt_copied_at']
) }}
OptionDefaultAccepted values
materializedrequiredincremental
incremental_strategymergemerge, append, insert_overwrite, replace_where, microbatch, delete+insert (1.11+)
unique_keyrequired for mergecolumn name or list
on_schema_changeignoreignore, fail, append_new_columns, sync_all_columns
incremental_predicatesNonelist of SQL predicates to narrow the merge scan
partition_byNonelist (required for insert_overwrite)
merge_exclude_columnsNonelist; columns that should not be updated on merge
merge_update_columnsNonelist; whitelist of columns to update
liquid_clustered_byNonelist of columns
tblproperties{}dict
databricks_computedefaultcompute name

Strategy compatibility

StrategyRequires unique_keyWorks with partition_byDelta-onlyNotes
mergeyesyes (optimizes merge)yesMost flexible, most expensive
appendnoyesno (works on Parquet/Hudi too)Cheapest; no uniqueness guarantee
insert_overwritenoyes (required)noRewrites touched partitions
replace_wherenonoyesUses incremental_predicates for scope
microbatchnoyes (by event_time)yes1.9+; keyed on event_time config
delete+insertyesyesyes1.11+; alternative to merge

on_schema_change behavior

ValueNew column upstreamColumn type changed upstreamColumn removed upstream
ignoreSilently droppedErrorSilently dropped
failErrorErrorError
append_new_columnsAdded to targetErrorSilently dropped
sync_all_columnsAddedError (use --full-refresh)Removed from target

Danger

ignore is a silent data-loss path. Prefer fail or append_new_columns. If you pick ignore, document why next to the config() block so the next reader understands it was deliberate.

materialized_view

{{ config(
    materialized='materialized_view',
    refresh_schedule={'cron': '0 * * * *', 'time_zone': 'UTC'},
    tblproperties={'pipelines.autoOptimize.managed': 'true'}
) }}
OptionDefaultAccepted values
materializedrequiredmaterialized_view
refresh_scheduleNonedict with cron + time_zone, or None for on-demand
tblproperties{}dict; pipelines.* keys work
databricks_computedefaultcompute name

Note

materialized_view delegates to Delta Live Tables. You give up direct control of the refresh mechanism; Databricks decides how to incrementalize. The upside is no is_incremental(), no unique_key debate, no schema-drift handling.

streaming_table

{{ config(
    materialized='streaming_table',
    tblproperties={'pipelines.reset.allowed': 'false'}
) }}

select * from stream({{ ref('events_raw') }})
OptionDefaultAccepted values
materializedrequiredstreaming_table
tblproperties{}dict; pipelines.* keys
databricks_computedefaultcompute name

Source must be a streaming-compatible Delta table or a registered streaming source. stream() wraps the ref.

ephemeral

{{ config(materialized='ephemeral') }}

Not a real materialization. dbt inlines the model's SQL as a CTE into every downstream model's compiled SQL.

OptionDefaultAccepted values
materializedrequiredephemeral
tags[]list

Has no storage, no alias, no schema. Cannot be tested directly; the tests fold into the downstream consumer's SQL.

Compute routing

Define named compute in profiles.yml:

outputs:
  prod:
    databricks_compute:
      default: { http_path: /sql/1.0/warehouses/serverless-small }
      heavy:   { http_path: /sql/1.0/warehouses/serverless-large }
      python:  { http_path: /api/2.1/clusters/all-purpose-cluster-id }

Pin a model to named compute:

{{ config(databricks_compute='heavy') }}

Serverless SQL warehouses are the right default for SQL models. All-purpose clusters are for Python models and interactive notebooks, not batch SQL.

Table properties (tblproperties)

Common ones:

PropertyEffect
delta.autoOptimize.optimizeWriteCombine small writes into larger files
delta.autoOptimize.autoCompactAutomatically compact after writes
delta.enableChangeDataFeedEnable CDF for downstream streaming consumers
delta.columnMapping.modename or id; enables column renames without rewriting data
pipelines.autoOptimize.managed(MV / streaming table) let Databricks manage optimize

See also