Snowflake configurations
Snowflake plans to increase the default column size for string and binary data types in September 2026. dbt-snowflake versions below v1.10.6 may fail to build certain incremental models when this change is deployed.
Iceberg table format
Our Snowflake Iceberg table content has moved to a new page!
Dynamic tables
The Snowflake adapter supports dynamic tables.
This materialization is specific to Snowflake, which means that any model configuration that
would normally come along for the ride from dbt-core (e.g. as with a view) may not be available
for dynamic tables. This gap will decrease in future patches and versions.
While this materialization is specific to Snowflake, it very much follows the implementation
of materialized views.
In particular, dynamic tables have access to the on_configuration_change setting.
Dynamic tables are supported with the following configuration parameters:
| Parameter | Type | Required | Default | Change Monitoring Support |
|---|---|---|---|---|
on_configuration_change | <string> | no | apply | n/a |
target_lag | <string> | no | alter | |
scheduler | <string> | no | DISABLE | alter |
snowflake_warehouse | <string> | yes | alter | |
snowflake_initialization_warehouse | <string> | no | None | alter |
refresh_warehouse | <string> | no | None | alter |
refresh_mode | <string> | no | AUTO | refresh |
initialize | <string> | no | ON_CREATE | n/a |
cluster_by | <string> or <list> | no | None | alter |
immutable_where | <string> | no | None | alter |
copy_grants | <boolean> | no | false | full refresh |
transient | <boolean> | no | false | full refresh |
- Project YAML file
- Properties YAML file
- SQL file config
models:
<resource-path>:
+materialized: dynamic_table
+on_configuration_change: apply | continue | fail
+target_lag: downstream | <time-delta>
+scheduler: ENABLE | DISABLE
+snowflake_warehouse: <warehouse-name>
+snowflake_initialization_warehouse: <warehouse-name>
+refresh_warehouse: <warehouse-name>
+refresh_mode: AUTO | FULL | INCREMENTAL
+initialize: ON_CREATE | ON_SCHEDULE
+cluster_by: <column-name> | [<column-name>, <column-name>, ...]
+immutable_where: <condition>
+copy_grants: true | false
+transient: true | false
models:
- name: [<model-name>]
config:
materialized: dynamic_table
on_configuration_change: apply | continue | fail
target_lag: downstream | <time-delta>
scheduler: ENABLE | DISABLE
snowflake_warehouse: <warehouse-name>
snowflake_initialization_warehouse: <warehouse-name>
refresh_warehouse: <warehouse-name>
refresh_mode: AUTO | FULL | INCREMENTAL
initialize: ON_CREATE | ON_SCHEDULE
cluster_by: <column-name> | [<column-name>, <column-name>, ...]
immutable_where: <condition>
copy_grants: true | false
transient: true | false
{{ config(
materialized="dynamic_table",
on_configuration_change="apply" | "continue" | "fail",
target_lag="downstream" | "<integer> seconds | minutes | hours | days",
scheduler="ENABLE" | "DISABLE",
snowflake_warehouse="<warehouse-name>",
snowflake_initialization_warehouse="<warehouse-name>",
refresh_warehouse="<warehouse-name>",
refresh_mode="AUTO" | "FULL" | "INCREMENTAL",
initialize="ON_CREATE" | "ON_SCHEDULE",
cluster_by="<column-name>" | ["<column-name>", "<column-name>", ...],
immutable_where="<condition>",
copy_grants=true | false,
transient=true | false,
) }}
Learn more about these parameters in Snowflake's docs:
Target lag
Snowflake allows two configuration scenarios for scheduling automatic refreshes:
- Time-based — Provide a value of the form
<int> { seconds | minutes | hours | days }. For example, if the dynamic table needs to be updated every 30 minutes, usetarget_lag='30 minutes'. - Downstream — Applicable when the dynamic table is referenced by other dynamic tables. In this scenario,
target_lag='downstream'allows for refreshes to be controlled at the target, instead of at each layer.
How target_lag interacts with scheduler
target_lag works with scheduler to determine how dynamic table refreshes are managed:
target_lag | scheduler | Behavior |
|---|---|---|
| Set | ENABLE or omitted | Snowflake manages refreshes automatically. If scheduler is omitted, dbt defaults to ENABLE. |
| Not set | DISABLE or omitted | dbt manages refreshes during model execution. If scheduler is omitted, dbt defaults to DISABLE. |
| Set | DISABLE | Invalid: DISABLE does not accept target_lag. dbt raises an error. |
| Not set | ENABLE | Invalid: ENABLE requires target_lag. dbt raises an error. |
Learn more about target_lag in Snowflake's docs. Please note that Snowflake supports a target lag of 1 minute or longer.
Scheduler
The scheduler parameter controls whether the dynamic table is refreshed by Snowflake's background scheduler or by an external orchestrator (for example, dbt). Snowflake accepts two options:
- ENABLE — Snowflake's built-in scheduler automatically refreshes the dynamic table based on the defined
target_lag. Refreshes cascade across the dependency graph to maintain snapshot consistency. Settingtarget_lagis required when using this option. - DISABLE — The dynamic table is excluded from Snowflake's automatic background refresh. You must trigger refreshes manually or through orchestration external to Snowflake (for example, by a
dbt runthat executesALTER DYNAMIC TABLE ... REFRESH). When this option is explicitly set, specifyingtarget_lagresults in an error.
In Snowflake's native DDL, omitting SCHEDULER defaults to ENABLE, and TARGET_LAG is required.
In dbt, the default value is DISABLE. If neither scheduler nor target_lag is specified, dbt creates the dynamic table with scheduler: DISABLE and manages refreshes directly.
If you specify target_lag without explicitly setting scheduler, dbt sets scheduler: ENABLE.
Key points:
- Explicitly setting
scheduler: DISABLEtogether withtarget_lagresults in an error. If you omitschedulerand providetarget_lag, dbt resolves the conflict by settingscheduler: ENABLEautomatically. - When
scheduler: DISABLE, a manual refresh does not automatically refresh upstream dynamic table dependencies. This creates an isolation boundary, allowing dbt to manage specific table refreshes without triggering the entire pipeline. In contrast,ENABLEcascades refreshes across the dependency graph. - If a dynamic table with
scheduler: DISABLEdepends on other dynamic tables, those upstream tables will not be refreshed when the downstream table is refreshed. dbt must manage the refresh order explicitly.
For example, to let dbt manage refreshes (default behavior):
{{ config(
materialized='dynamic_table',
snowflake_warehouse='MY_WH',
) }}
select * from {{ source('raw', 'events') }}
To enable Snowflake-managed scheduling with a target lag:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='MY_WH',
target_lag='5 minutes',
) }}
select * from {{ source('raw', 'events') }}
Learn more about scheduler in Snowflake's docs.
Refresh warehouse
Starting dbt-snowflake v1.11, you can use the refresh_warehouse parameter in your model configuration to specify a separate warehouse for the dynamic table's self-refresh operations. This is separate from snowflake_warehouse, which controls DDL execution. By setting refresh_warehouse, you can use a smaller warehouse for automatic refreshes while keeping a larger snowflake_warehouse for DDL operations.
To configure the refresh_warehouse parameter in your model, refer to the following example:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='LARGE_EXECUTION_WH',
refresh_warehouse='SMALL_REFRESH_WH',
target_lag='1 hour'
) }}
select * from {{ source('raw', 'events') }}
Key points:
- If
refresh_warehouseis not set,snowflake_warehouseis used for both DDL execution and self-refresh operations. - You can change
refresh_warehouseon an existing dynamic table without a full refresh. - To revert to the default behavior after setting a refresh warehouse, remove the
refresh_warehouseparameter from your model configuration or explicitly set it toNone.
Learn more about the WAREHOUSE parameter in Snowflake's docs.
Refresh mode
Snowflake allows three options for refresh mode:
- AUTO — Enforces an incremental refresh of the dynamic table by default. If the
CREATE DYNAMIC TABLEstatement does not support the incremental refresh mode, the dynamic table is automatically created with the full refresh mode. - FULL — Enforces a full refresh of the dynamic table, even if the dynamic table can be incrementally refreshed.
- INCREMENTAL — Enforces an incremental refresh of the dynamic table. If the query that underlies the dynamic table can’t perform an incremental refresh, dynamic table creation fails and displays an error message.
Learn more about refresh_mode in Snowflake's docs.
Initialize
Snowflake allows two options for initialize:
- ON_CREATE — Refreshes the dynamic table synchronously at creation. If this refresh fails, dynamic table creation fails and displays an error message.
- ON_SCHEDULE — Refreshes the dynamic table at the next scheduled refresh.
Learn more about initialize in Snowflake's docs.
Immutable where
Snowflake allows you to mark certain rows of a dynamic table as immutable using the IMMUTABLE WHERE clause. This prevents Snowflake from applying updates or deletions to matching rows during refreshes, so historical data stays the same and refreshes run faster.
From dbt Core v1.11, you can configure this using the immutable_where configuration. This config accepts a SQL condition expression and rows that match it are treated as immutable and won’t be updated or deleted during future refreshes.
For example, to mark data older than 1 day as immutable since historical data typically doesn't change:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='MY_WH',
target_lag='1 hour',
immutable_where='ts < CURRENT_TIMESTAMP() - INTERVAL \'1 DAY\''
) }}
select
id,
ts,
value
from {{ source('raw', 'events') }}
Key points:
- The config supports Jinja rendering (for example, dbt variables and macros), as long as the rendered result is a valid Snowflake SQL condition.
- To remove the immutable constraint from an existing dynamic table, set
immutable_wheretoNone. - You can alter changes to
immutable_wherewithout a full refresh.
Learn more about IMMUTABLE WHERE in Snowflake's docs.
Copy grants (dynamic tables)
Starting dbt-snowflake v1.11, you can use copy_grants to preserve existing object-level privileges when dbt generates a CREATE OR REPLACE DYNAMIC TABLE statement. When disabled, all previously granted permissions are dropped when the table is recreated, and downstream users or roles lose access until grants are manually re-applied.
When you set copy_grants: true on a dynamic table, dbt adds the COPY GRANTS clause to the CREATE OR REPLACE DYNAMIC TABLE statement. This preserves existing object-level privileges on the table during --full-refresh runs, so you don't need to re-grant access after the table is recreated.
To configure the copy_grants parameter, refer to the following example:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='MY_WH',
target_lag='1 hour',
copy_grants=true
) }}
select * from {{ source('raw', 'events') }}
Learn more about COPY GRANTS in Snowflake's docs.
Transient (dynamic tables)
You can create dynamic tables as transient to reduce storage costs. Transient dynamic tables do not use Snowflake's Fail-safe period, so they consume less storage than permanent dynamic tables. To create a dynamic table as transient, set transient: true in the model configuration.
If you want all dynamic tables to be transient by default (without setting transient: true on each one), enable the snowflake_default_transient_dynamic_tables flag in your dbt_project.yml. This flag defaults to false, meaning dynamic tables are created as permanent by default.
Key points:
- Setting
transient: truecreates the dynamic table with theTRANSIENTkeyword in theCREATE DYNAMIC TABLEstatement. - Snowflake does not support changing the transient property on an existing dynamic table. Changing
transientfromtruetofalseor vice versa triggers a full table recreation. - To make all new dynamic tables transient by default when
transientis not specified, enable thesnowflake_default_transient_dynamic_tablesflag in yourdbt_project.yml.
For example:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='MY_WH',
target_lag='1 hour',
transient=true
) }}
select * from {{ source('raw', 'events') }}
Initialization warehouse
Snowflake supports an INITIALIZATION_WAREHOUSE parameter that specifies which virtual warehouse to use when initializing or reinitializing a dynamic table.
Starting dbt-snowflake v1.12, you can use the snowflake_initialization_warehouse parameter to configure this. This is separate from the snowflake_warehouse parameter used for regular incremental refreshes. By setting snowflake_initialization_warehouse, you can use a larger warehouse for the initial build and reinitialization, while keeping snowflake_warehouse smaller for regular refreshes.
To configure the snowflake_initialization_warehouse parameter, refer to the following example:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='COMPUTE_WH',
snowflake_initialization_warehouse='LARGE_WH',
target_lag='1 minute'
) }}
select * from {{ source('raw', 'events') }}
Key points:
- If
snowflake_initialization_warehouseis not set, Snowflake usessnowflake_warehousefor both initialization and regular refreshes. - You can change
snowflake_initialization_warehouseon an existing dynamic table without a full refresh. - To revert to the default behavior after setting an initialization warehouse, either remove the
snowflake_initialization_warehouseparameter from your model configuration or explicitly set it toNone.
Learn more about INITIALIZATION_WAREHOUSE in Snowflake's docs.
Limitations
As with materialized views on most data platforms, there are limitations associated with dynamic tables. Some worth noting include:
- Dynamic table SQL has a limited feature set.
- Dynamic table SQL cannot be updated; the dynamic table must go through a
--full-refresh(DROP/CREATE). - Dynamic tables cannot be downstream from: materialized views, external tables, streams.
- Dynamic tables cannot reference a view that is downstream from another dynamic table.
Find more information about dynamic table limitations in Snowflake's docs.
For dbt limitations, Model contracts are not supported.
Troubleshooting dynamic tables
If your dynamic table model fails to rerun with the following error message after the initial execution:
SnowflakeDynamicTableConfig.__init__() missing 6 required positional arguments: 'name', 'schema_name', 'database_name', 'query', 'target_lag', and 'snowflake_warehouse'
Ensure that QUOTED_IDENTIFIERS_IGNORE_CASE on your account is set to FALSE.
Semantic Views
Snowflake Semantic Views provide a native schema-level object for centralizing metric definitions and reducing fragmented metric logic across BI and analytics tools.
Use the dbt_semantic_view package to define and manage Snowflake Semantic Views in your dbt project. This lets you keep Semantic View definitions in version control and apply your existing testing and CI/CD workflows to your Semantic Layer.
Install the package
- This package requires
dbtversion>=1.0.0, <2.0.0. For the latest compatibility details, refer to thedbt_semantic_viewpackage. - Your Snowflake account supports Semantic Views.
- Your role has permission to create Semantic Views.
- You can write to a database and schema where you have create privileges.
Add dbt_semantic_view to your packages.yml file:
packages:
- package: Snowflake-Labs/dbt_semantic_view
version: 1.0.3
Run dbt deps to install package dependencies:
dbt deps
Verify the package was installed by confirming dbt_semantic_view is present in your dbt_packages/ directory.
Highlighted features
The dbt_semantic_view package includes the following features for defining and managing Snowflake Semantic Views in dbt projects.
Materialize models as Snowflake Semantic Views
Use the semantic_view materialization to define Snowflake Semantic Views in dbt, including tables, relationships, facts, dimensions, and metrics.
Semantic view models use Snowflake’s semantic view syntax (for example, TABLES, DIMENSIONS, and METRICS) rather than a standard SELECT query.
The example below is adapted from Getting Started with Snowflake Semantic View.
{{ config(materialized='semantic_view') }}
tables (
CUSTOMER as {{ SOURCE('<SOURCE_NAME>', 'CUSTOMER') }} primary key (C_CUSTOMER_SK),
DATE as {{ SOURCE('<SOURCE_NAME>', 'DATE_DIM') }} primary key (D_DATE_SK),
DEMO as {{ SOURCE('<SOURCE_NAME>', 'CUSTOMER_DEMOGRAPHICS') }} primary key (CD_DEMO_SK),
ITEM as {{ SOURCE('<SOURCE_NAME>', 'ITEM') }} primary key (I_ITEM_SK),
STORE as {{ SOURCE('<SOURCE_NAME>', 'STORE') }} primary key (S_STORE_SK),
STORESALES as {{ SOURCE('<SOURCE_NAME>', 'STORESALES') }}
primary key (SS_SOLD_DATE_SK,SS_CDEMO_SK,SS_ITEM_SK,SS_STORE_SK,SS_CUSTOMER_SK)
)
relationships (
SALESTOCUSTOMER as STORESALES(SS_CUSTOMER_SK) references CUSTOMER(C_CUSTOMER_SK),
SALESTODATE as STORESALES(SS_SOLD_DATE_SK) references DATE(D_DATE_SK),
SALESTODEMO as STORESALES(SS_CDEMO_SK) references DEMO(CD_DEMO_SK),
SALESTOITEM as STORESALES(SS_ITEM_SK) references ITEM(I_ITEM_SK),
SALETOSTORE as STORESALES(SS_STORE_SK) references STORE(S_STORE_SK)
)
facts (
ITEM.COST as i_wholesale_cost,
ITEM.PRICE as i_current_price,
STORE.TAX_RATE as S_TAX_PERCENTAGE,
STORESALES.SALES_QUANTITY as SS_QUANTITY
)
dimensions (
CUSTOMER.BIRTHYEAR as C_BIRTH_YEAR,
CUSTOMER.COUNTRY as C_BIRTH_COUNTRY,
CUSTOMER.C_CUSTOMER_SK as c_customer_sk,
DATE.DATE as D_DATE,
DATE.D_DATE_SK as d_date_sk,
DATE.MONTH as D_MOY,
DATE.WEEK as D_WEEK_SEQ,
DATE.YEAR as D_YEAR,
DEMO.CD_DEMO_SK as cd_demo_sk,
DEMO.CREDIT_RATING as CD_CREDIT_RATING,
DEMO.MARITAL_STATUS as CD_MARITAL_STATUS,
ITEM.BRAND as I_BRAND,
ITEM.CATEGORY as I_CATEGORY,
ITEM.CLASS as I_CLASS,
ITEM.I_ITEM_SK as i_item_sk,
STORE.MARKET as S_MARKET_ID,
STORE.SQUAREFOOTAGE as S_FLOOR_SPACE,
STORE.STATE as S_STATE,
STORE.STORECOUNTRY as S_COUNTRY,
STORE.S_STORE_SK as s_store_sk,
STORESALES.SS_CDEMO_SK as ss_cdemo_sk,
STORESALES.SS_CUSTOMER_SK as ss_customer_sk,
STORESALES.SS_ITEM_SK as ss_item_sk,
STORESALES.SS_SOLD_DATE_SK as ss_sold_date_sk,
STORESALES.SS_STORE_SK as ss_store_sk
)
metrics (
STORESALES.TOTALCOST as SUM(item.cost),
STORESALES.TOTALSALESPRICE as SUM(SS_SALES_PRICE),
STORESALES.TOTALSALESQUANTITY as SUM(SS_QUANTITY)
WITH SYNONYMS = ('total sales quantity', 'total sales amount')
)
When you run dbt, this model compiles to a Snowflake CREATE SEMANTIC VIEW statement.
Reference Semantic Views in other dbt models
Use ref() for Semantic Views defined in your dbt project, and use source() for existing external Semantic Views.
{{ config(materialized='view') }}
select * from semantic_view(
{{ ref('<semantic_view_model_name>') }}
METRICS ...
DIMENSIONS ...
WHERE ...
)
{{ config(materialized='table') }}
select * from semantic_view(
{{ source('<source_name>', '<semantic_view>') }}
METRICS ...
DIMENSIONS ...
WHERE ...
)
Temporary tables
To save compile time and avoid the database write step initiated by a temporary table, incremental table merges for Snowflake prefer to utilize a view rather than a temporary table .
Sometimes a temporary table achieves results faster or more safely. You can opt in to temporary or transient tables for incremental builds by using the tmp_relation_type configuration This is defined as part of the model configuration.
To guarantee accuracy, an incremental model using the delete+insert strategy with a unique_key defined requires a temporary table; trying to change this to a view will result in an error.
tmp_relation_type accepts these values:
view(default): Skips intermediate step of creating a temporary physical table for the tmp relation; fastest but not suitable for all strategies.table: A session-scoped temporary table; not visible in the Snowflake catalog and isolated per session.transient: A transient table; persists in the catalog, enabling Snowflake native lineage tracking, while avoiding the 7-day fail-safe storage costs of permanent tables. Note: This value is distinct from the separate model-leveltransientconfig described later, which controls the final model relation.
Defined in the project YAML:
name: my_project
...
models:
<resource-path>:
+tmp_relation_type: table | view | transient ## If not defined, view is the default.
In the configuration format for the model SQL file:
{{ config(
tmp_relation_type="table | view | transient",
-- If not defined, view is the default.
) }}
transientWhen tmp_relation_type is set to transient, the tmp relation becomes a real table that persists in the target schema under a deterministic name. If multiple runs of the same incremental model execute concurrently in the same schema, they can overwrite each other's tmp relation, causing data duplication or incorrect results. For example, this might happen when developers share a target schema or when CI and production runs overlap.
This risk depends on how you configure schemas and databases for your dbt models. To prevent conflicts, use snowflake__resolve_incremental_tmp_relation to route tmp relations to a schema that is unique per run or environment. For more information, refer to Avoiding tmp relation conflicts.
Avoiding tmp relation conflicts
To prevent name collisions across concurrent runs, override the snowflake__resolve_incremental_tmp_relation dispatch macro to redirect the tmp relation to a dedicated schema:
{% macro snowflake__resolve_incremental_tmp_relation(tmp_relation) %}
{{ return(tmp_relation.incorporate(schema='scratch')) }}
{% endmacro %}
This macro receives the default tmp relation object and returns a modified version. Common overrides include appending a developer username, a CI job ID, or a target name to the schema to ensure isolation across concurrent runs.
To append a target name to the schema:
{% macro snowflake__resolve_incremental_tmp_relation(tmp_relation) %}
{%- set scratch_schema = target.schema ~ '_scratch_' ~ env_var('DBT_JOB_ID', target.name) -%}
{{ return(tmp_relation.incorporate(schema=scratch_schema)) }}
{% endmacro %}
Transient tables
Snowflake supports the creation of transient tables. Snowflake does not preserve a history for these tables, which can result in a measurable reduction of your Snowflake storage costs. Transient tables participate in time travel to a limited degree with a retention period of 1 day by default with no fail-safe period. Weigh these tradeoffs when deciding whether or not to configure your dbt models as transient. By default, all Snowflake tables created by dbt are transient.
Configuring transient tables in dbt_project.yml
A whole folder (or package) can be configured to be transient (or not) by adding a line to the dbt_project.yml file. This config works just like all of the model configs defined in dbt_project.yml.
name: my_project
...
models:
+transient: false
my_project:
...
Configuring transience for a specific model
A specific model can be configured to be transient by setting the transient model config to true.
{{ config(materialized='table', transient=true) }}
select * from ...
Query tags
Query tags are a Snowflake parameter that can be quite useful later on when searching in the QUERY_HISTORY view.
dbt supports setting a default query tag for the duration of its Snowflake connections in
your profile. You can set more precise values (and override the default) for subsets of models by setting
a query_tag model config or by overriding the default set_query_tag macro:
models:
<resource-path>:
+query_tag: dbt_special
{{ config(
query_tag = 'dbt_special'
) }}
select ...
In this example, you can set up a query tag to be applied to every query with the model's name.
{% macro set_query_tag() -%}
{% set new_query_tag = model.name %}
{% if new_query_tag %}
{% set original_query_tag = get_current_query_tag() %}
{{ log("Setting query_tag to '" ~ new_query_tag ~ "'. Will reset to '" ~ original_query_tag ~ "' after materialization.") }}
{% do run_query("alter session set query_tag = '{}'".format(new_query_tag)) %}
{{ return(original_query_tag)}}
{% endif %}
{{ return(none)}}
{% endmacro %}
Note: query tags are set at the session level. At the start of each model materialization, if the model has a custom query_tag configured, dbt will run alter session set query_tag to set the new value. At the end of the materialization, dbt will run another alter statement to reset the tag to its default value. As such, build failures midway through a materialization may result in subsequent queries running with an incorrect tag.
Merge behavior (incremental models)
The incremental_strategy config controls how dbt builds incremental models. By default, dbt will use a merge statement on Snowflake to refresh incremental tables.
Snowflake supports the following incremental strategies:
merge(default)appenddelete+insertinsert_overwrite- Note: This is not a standard dbt incremental strategy.
insert_overwritebehaves liketruncate+ re-insertcommands on Snowflake. It doesn't support partition-based overwrites, which means it'll overwrite the entire table intentionally. It's implemented as an incremental strategy because it aligns with dbt's workflow of not dropping existing tables. You can useoverwrite_columnsto control which columns are included in theINSERT OVERWRITEstatement.
- Note: This is not a standard dbt incremental strategy.
microbatch
Snowflake's merge statement fails with a "nondeterministic merge" error if the unique_key specified in your model config is not actually unique. If you encounter this error, you can instruct dbt to use a two-step incremental approach by setting the incremental_strategy config for your model to delete+insert.
overwrite_columns
When using incremental_strategy='insert_overwrite' on Snowflake, you can set overwrite_columns to control how dbt generates the INSERT OVERWRITE statement for your incremental model. For example:
{{ config(
materialized='incremental',
incremental_strategy='insert_overwrite',
overwrite_columns=['id', 'value', 'event_date']
) }}
select id, value, event_date
from {{ ref('my_source') }}
-
If you set
overwrite_columns, dbt generates SQL that explicitly lists the columns in both theINSERTtarget and theSELECTprojection:insert overwrite into my_schema.my_table (id, value, event_date)
select id, value, event_date
from staging_table -
If you don't set
overwrite_columns, dbt currently defaults toSELECT *:insert overwrite into my_schema.my_table
select *
from staging_table
Configuring table clustering
dbt supports table clustering on Snowflake. To control clustering for a table or incremental model, use the cluster_by config. When this configuration is applied, dbt will do two things:
- It will implicitly order the table results by the specified
cluster_byfields. - It will add the specified clustering keys to the target table.
By using the specified cluster_by fields to order the table, dbt minimizes the amount of work required by Snowflake's automatic clustering functionality. If an incremental model is configured to use table clustering, then dbt will also order the staged dataset before merging it into the destination table. As such, the dbt-managed table should always be in a mostly clustered state.
Using cluster_by
The cluster_by config accepts either a string, or a list of strings to use as clustering keys. The following example will create a sessions table that is clustered by the session_start column.
{{
config(
materialized='table',
cluster_by=['session_start']
)
}}
select
session_id,
min(event_time) as session_start,
max(event_time) as session_end,
count(*) as count_pageviews
from {{ source('snowplow', 'event') }}
group by 1
The code above will be compiled to SQL that looks (approximately) like this:
create or replace table my_database.my_schema.my_table as (
select * from (
select
session_id,
min(event_time) as session_start,
max(event_time) as session_end,
count(*) as count_pageviews
from {{ source('snowplow', 'event') }}
group by 1
)
-- this order by is added by dbt in order to create the
-- table in an already-clustered manner.
order by session_start
);
alter table my_database.my_schema.my_table cluster by (session_start);
Dynamic table clustering
Starting in dbt Core v1.11, dynamic tables support the cluster_by configuration. When set, dbt includes the clustering specification in the CREATE DYNAMIC TABLE statement.
For example:
{{ config(
materialized='dynamic_table',
snowflake_warehouse='COMPUTE_WH',
target_lag='1 minute',
cluster_by=['session_start', 'user_id']
) }}
select
session_id,
user_id,
min(event_time) as session_start,
max(event_time) as session_end,
count(*) as count_pageviews
from {{ source('snowplow', 'event') }}
group by 1, 2
This config generates the following SQL when compiled:
create or replace dynamic table my_database.my_schema.my_table
target_lag = '1 minute'
warehouse = COMPUTE_WH
cluster by (session_start, user_id)
as (
select
session_id,
user_id,
min(event_time) as session_start,
max(event_time) as session_end,
count(*) as count_pageviews
from source_table
group by 1, 2
);
You can specify clustering for dynamic tables when you create them using CLUSTER BY in the CREATE DYNAMIC TABLE statement. You don’t need to run a separate ALTER TABLE statement.
Automatic clustering
Automatic clustering is enabled by default in Snowflake today, no action is needed to make use of it. Though there is an automatic_clustering config, it has no effect except for accounts with (deprecated) manual clustering enabled.
If manual clustering is still enabled for your account, you can use the automatic_clustering config to control whether or not automatic clustering is enabled for dbt models. When automatic_clustering is set to true, dbt will run an alter table <table name> resume recluster query after building the target table.
The automatic_clustering config can be specified in the dbt_project.yml file, or in a model config() block.
models:
+automatic_clustering: true
Python model configuration
The Snowflake adapter supports Python models. Snowflake uses its own framework, Snowpark, which has many similarities to PySpark.
Additional setup: You will need to acknowledge and accept Snowflake Third Party Terms to use Anaconda packages.
Installing packages: Snowpark supports several popular packages via Anaconda. Refer to the complete list for more details. Packages are installed when your model is run. Different models can have different package dependencies. If you use third-party packages, Snowflake recommends using a dedicated virtual warehouse for best performance rather than one with many concurrent users.
Python version: To specify a different Python version, use the following configuration:
def model(dbt, session):
dbt.config(
materialized = "table",
python_version="3.11"
)
You can use the python_version config to run a Snowpark model with Python versions 3.9, 3.10, or 3.11.
External access integrations and secrets: To query external APIs within dbt Python models, use Snowflake’s external access together with secrets. Here are some additional configurations you can use:
import pandas
import snowflake.snowpark as snowpark
def model(dbt, session: snowpark.Session):
dbt.config(
materialized="table",
secrets={"secret_variable_name": "test_secret"},
external_access_integrations=["test_external_access_integration"],
)
import _snowflake
return session.create_dataframe(
pandas.DataFrame(
[{"secret_value": _snowflake.get_generic_secret_string('secret_variable_name')}]
)
)
Docs: "Developer Guide: Snowpark Python"
Third-party Snowflake packages
To use a third-party Snowflake package that isn't available in Snowflake Anaconda, upload your package by following this example, and then configure the imports setting in the dbt Python model to reference to the zip file in your Snowflake staging.
Here’s a complete example configuration using a zip file, including using imports in a Python model:
def model(dbt, session):
# Configure the model
dbt.config(
materialized="table",
imports=["@mystage/mycustompackage.zip"], # Specify the external package location
)
# Example data transformation using the imported package
# (Assuming `some_external_package` has a function we can call)
data = {
"name": ["Alice", "Bob", "Charlie"],
"score": [85, 90, 88]
}
df = pd.DataFrame(data)
# Process data with the external package
df["adjusted_score"] = df["score"].apply(lambda x: some_external_package.adjust_score(x))
# Return the DataFrame as the model output
return df
For more information on using this configuration, refer to Snowflake's documentation on uploading and using other python packages in Snowpark not published on Snowflake's Anaconda channel.
Configuring virtual warehouses
The default warehouse that dbt uses can be configured in your Profile for Snowflake connections. To override the warehouse that is used for specific models (or groups of models), use the snowflake_warehouse model configuration. This configuration can be used to specify a larger warehouse for certain models in order to control Snowflake costs and project build times.
Tests also supports the snowflake_warehouse configuration. This can be useful when you want to you run tests on a different Snowflake virtual warehouse than the one used to build models, for example, using a smaller warehouse for lightweight data tests while models run on a larger warehouse.
- Project file
- Property file
- SQL file config
The following example changes the warehouse for a group of models with a config argument in the YAML.
name: my_project
version: 1.0.0
...
models:
+snowflake_warehouse: "EXTRA_SMALL" # default Snowflake virtual warehouse for all models in the project.
my_project:
clickstream:
+snowflake_warehouse: "EXTRA_LARGE" # override the default Snowflake virtual warehouse for all models under the `clickstream` directory.
snapshots:
+snowflake_warehouse: "EXTRA_LARGE" # all Snapshot models are configured to use the `EXTRA_LARGE` warehouse.
data_tests:
+snowflake_warehouse: "EXTRA_SMALL" # all data tests are configured to use the `EXTRA_SMALL` warehouse.
The following example overrides the Snowflake warehouse for a single model and a specific test using a config argument in the property file.
models:
- name: my_model
config:
snowflake_warehouse: "EXTRA_LARGE" # override the Snowflake virtual warehouse just for this model
columns:
- name: id
data_tests:
- unique:
config:
snowflake_warehouse: "EXTRA_SMALL" # use a smaller warehouse for this test
The following example changes the warehouse for a single model with a config() block in the SQL model.
# override the Snowflake virtual warehouse for just this model
{{
config(
materialized='table',
snowflake_warehouse='EXTRA_LARGE'
)
}}
with
aggregated_page_events as (
select
session_id,
min(event_time) as session_start,
max(event_time) as session_end,
count(*) as count_page_views
from {{ source('snowplow', 'event') }}
group by 1
),
index_sessions as (
select
*,
row_number() over (
partition by session_id
order by session_start
) as page_view_in_session_index
from aggregated_page_events
)
select * from index_sessions
Copying grants
When the copy_grants config is set to true, dbt will add the copy grants DDL qualifier when rebuilding tables, views, and dynamic tables (dbt-snowflake v1.11 and later). The default value is false.
models:
+copy_grants: true
Setting row access policies
Configure row access policies on tables, views, and dynamic tables by using the row_access_policy config for models. The policy must already exist in Snowflake before you apply it to the model.
{{ config(
row_access_policy = 'my_database.my_schema.my_row_access_policy_name on (id)'
) }}
select ...
Configuring table tags
To add tags to tables, views, and dynamic tables, use the table_tag config. Note, the tag must already exist in Snowflake before you apply it.
{{ config(
table_tag = "my_tag_name = 'my_tag_value'"
) }}
select ...
Secure views
To create a Snowflake secure view, use the secure config for view models. Secure views can be used to limit access to sensitive data. Note: secure views may incur a performance penalty, so you should only use them if you need them.
The following example configures the models in the sensitive/ folder to be configured as secure views.
name: my_project
version: 1.0.0
models:
my_project:
sensitive:
+materialized: view
+secure: true
Source freshness known limitation
Snowflake calculates source freshness using information from the LAST_ALTERED column, meaning it relies on a field updated whenever any object undergoes modification, not only data updates. No action must be taken, but analytics teams should note this caveat.
Per the Snowflake documentation:
(Applies to dbt v1.9 and later)The
LAST_ALTEREDcolumn is updated when the following operations are performed on an object:
- DDL operations.
- DML operations (for tables only).
- Background maintenance operations on metadata performed by Snowflake.
Pagination for object results
By default, when dbt encounters a schema with up to 100,000 objects, it will paginate the results from show objects at 10,000 per page for up to 10 pages.
Environments with more than 100,000 objects in a schema can customize the number of results per page and the page limit using the following flags in the dbt_project.yml:
list_relations_per_page— The number of relations on each page (Max 10k as this is the most Snowflake allows).list_relations_page_limit— The maximum number of pages to include in the results.
For example, if you wanted to include 10,000 objects per page and include up to 100 pages (1 million objects), configure the flags as follows:
flags:
list_relations_per_page: 10000
list_relations_page_limit: 100
Was this page helpful?
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.