Trying Out Everything from Creating Semantic Views to Managing Materialization Using the dbt_semantic_view Package in dbt Projects on Snowflake

Trying Out Everything from Creating Semantic Views to Managing Materialization Using the dbt_semantic_view Package in dbt Projects on Snowflake

I tried Snowflake-Labs' dbt_semantic_view package in dbt Projects on Snowflake. This covers implementation and test results, from creating a Semantic View to referencing it downstream and managing materialization declaratively.
2026.11.11

This page has been translated by machine translation. View original

I'm Kawabata.

In this article, I use the dbt package dbt_semantic_view, published by Snowflake-Labs, with dbt Projects on Snowflake. From a Snowsight Workspace, I verify creating a Semantic View, referencing it from downstream models, and managing materializations declaratively.

https://hub.getdbt.com/Snowflake-Labs/dbt_semantic_view/latest/

https://github.com/Snowflake-Labs/dbt_semantic_view

https://docs.snowflake.com/en/user-guide/data-engineering/dbt-projects-on-snowflake

Overview of the dbt_semantic_view package

dbt_semantic_view is a dbt package published by Snowflake-Labs under Apache-2.0.

It adds a materialization called materialized='semantic_view'. You write everything after the TABLES clause of CREATE SEMANTIC VIEW as-is in the model's SQL file, and put ref() or source() where the table names go. dbt prepends CREATE OR REPLACE SEMANTIC VIEW <model name> and executes it. Because Snowflake SQL is passed through unchanged, you can use new clauses as they are added without waiting for a package update. There are four configs.

config Default Purpose
copy_grants false Appends COPY GRANTS to the end of CREATE OR REPLACE
create_or_alter false Uses CREATE OR ALTER instead of CREATE OR REPLACE
max_staleness None Appends MAX_STALENESS = '<value>' at the end. Materializations require MAX_STALENESS, and you specify it in either the config or the SQL body, not both
sv_materializations None YAML definition of materializations. Synchronized on every run with SYSTEM$MANAGE_SEMANTIC_VIEW_MATERIALIZATIONS_FROM_YAML

Two generic tests are also included (materialization_exists and materialization_is_active). They check whether a materialization exists and what state it is in. persist_docs is not supported.

Can it be used with dbt Projects on Snowflake?

The package consists only of Jinja macros, with no dependency on Python or other packages. Custom materializations are a standard dbt Core mechanism, so they work as-is on the dbt Core runtime managed by Snowflake. The limitations page also has no statement restricting custom materializations or packages. There are two points to note in advance.

  • To fetch packages from dbt Hub with dbt deps inside Snowflake, an External Access Integration is required. Because external access is disabled by default on trial accounts, this article places the package as a local package
  • On the dbt Fusion (2.x) runtime, config keys such as create_or_alter are rejected

https://docs.snowflake.com/en/user-guide/data-engineering/dbt-projects-on-snowflake-dependencies

This article proceeds with dbt Core 1.12.3.

Prerequisites

  • An account where dbt Projects on Snowflake and Semantic Views are available. No edition requirement is documented, and I confirmed this on trial accounts (Business Critical and Enterprise)
  • For the role that runs dbt: CREATE SEMANTIC VIEW, ADD SEMANTIC VIEW MATERIALIZATION, and CREATE DBT PROJECT on the target schema
  • For the same role: USAGE on the warehouse and read privileges on SNOWFLAKE_SAMPLE_DATA
  • Semantic View materializations are a preview feature (available in all accounts). The minimum value of MAX_STALENESS is 120 seconds

I verified this on October 29, 2026 on a trial account in the AWS Tokyo region. The behavior of the generic tests and Fusion was additionally confirmed on another trial account on November 11, 2026.
I ran dbt commands in a Snowsight Workspace, and the runtime is dbt Core 1.12.3 (dbt-snowflake 1.12.0).

Preparation

Objects on the Snowflake side

Using ACCOUNTADMIN, create the execution role, the DB and schema, and the warehouse, and grant the required privileges individually. ADD SEMANTIC VIEW MATERIALIZATION is a schema privilege for adding and dropping materializations.

USE ROLE ACCOUNTADMIN;

CREATE ROLE DBT_SV_ROLE;
GRANT ROLE DBT_SV_ROLE TO ROLE SYSADMIN;
GRANT ROLE DBT_SV_ROLE TO USER <自分のユーザー名>;

CREATE WAREHOUSE DBT_SV_WH WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 INITIALLY_SUSPENDED = TRUE;
CREATE DATABASE DBT_SV_DB;
CREATE SCHEMA DBT_SV_DB.DEV;

GRANT USAGE ON WAREHOUSE DBT_SV_WH TO ROLE DBT_SV_ROLE;
GRANT USAGE ON DATABASE DBT_SV_DB TO ROLE DBT_SV_ROLE;
GRANT USAGE, CREATE TABLE, CREATE VIEW, CREATE SEMANTIC VIEW, ADD SEMANTIC VIEW MATERIALIZATION,
      CREATE DBT PROJECT, CREATE STAGE, CREATE TASK ON SCHEMA DBT_SV_DB.DEV TO ROLE DBT_SV_ROLE;
GRANT EXECUTE TASK ON ACCOUNT TO ROLE DBT_SV_ROLE;
GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE_SAMPLE_DATA TO ROLE DBT_SV_ROLE;

How to place the package

To fetch packages from dbt Hub with dbt deps inside Snowflake, an External Access Integration is required. The allowed destinations are hub.getdbt.com and codeload.github.com, specified at deployment time.
On trial accounts, external access is disabled by default, and enabling it requires a request to your account representative. In my environment, I could create the network rule, but creating the External Access Integration resulted in an error.

This article uses the local package approach, placing the package source in local_packages/. Since dbt deps on the Snowflake side succeeds without network access, everything is completed within the Workspace alone.

dbt project

The project placed in the Workspace has the following structure. I don't use seeds; TPCH_SF1 in SNOWFLAKE_SAMPLE_DATA is used as the source.

dbt_sv_demo/
├── dbt_project.yml
├── profiles.yml
├── packages.yml
├── local_packages/dbt_semantic_view/     # Copy only dbt_project.yml, LICENSE, and macros/ from GitHub v1.0.6
└── models/
    ├── sources.yml                       # tpch: orders / customer from SNOWFLAKE_SAMPLE_DATA.TPCH_SF1
    ├── staging/stg_orders.sql, stg_customers.sql
    ├── semantic/sv_tpch_sales.sql, schema.yml
    └── marts/revenue_by_segment_year.sql

local_packages/dbt_semantic_view/ needs only three things: dbt_project.yml, LICENSE, and macros/. integration_tests/ and docs/ are unnecessary, and the whole project fits in 18 files. packages.yml uses a local reference.

packages:
  - local: local_packages/dbt_semantic_view

In dbt Projects on Snowflake, profiles.yml is placed at the project root. Since execution happens inside Snowflake, account and user can be placeholders.

dbt_sv_demo:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: "_"
      user: "_"
      role: DBT_SV_ROLE
      database: DBT_SV_DB
      schema: DEV
      warehouse: DBT_SV_WH
      threads: 4

In dbt_project.yml, staging and marts are set to +materialized: table. The staging models only align the TPCH column names.

-- models/staging/stg_orders.sql
select
    o_orderkey      as order_key,
    o_custkey       as customer_key,
    o_orderdate     as order_date,
    o_orderstatus   as order_status,
    o_orderpriority as order_priority,
    o_totalprice    as order_total
from {{ source('tpch', 'orders') }}

stg_customers.sql is similar. c_custkey becomes customer_key, c_name becomes customer_name, and c_mktsegment becomes market_segment.

Trying it out

Place the project in the Workspace and run dbt deps

Create a Workspace under Projects » Workspaces in Snowsight and place the files above. Change the dbt runtime from the account default to 1.12.3.

Next, run dbt deps. Since the package is in local_packages/, dbt_packages/dbt_semantic_view/ is created without using the network.

Create the Semantic View model and run dbt build

Create models/semantic/sv_tpch_sales.sql. First, here is the basic form without materializations. It uses TPCH orders and customer as logical tables and defines 5 dimensions and 3 metrics.

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

TABLES (
    orders    AS {{ ref('stg_orders') }}    PRIMARY KEY (order_key)    COMMENT = 'TPC-H orders',
    customers AS {{ ref('stg_customers') }} PRIMARY KEY (customer_key) COMMENT = 'TPC-H customers'
)
RELATIONSHIPS (
    orders_to_customers AS orders (customer_key) REFERENCES customers (customer_key)
)
FACTS (
    orders.order_total AS order_total COMMENT = 'Order total price'
)
DIMENSIONS (
    orders.order_date        AS order_date,
    orders.order_year        AS YEAR(order_date),
    orders.order_status      AS order_status,
    customers.customer_name  AS customer_name,
    customers.market_segment AS market_segment
)
METRICS (
    orders.total_revenue       AS SUM(order_total)  COMMENT = 'Sum of order total price',
    orders.order_count         AS COUNT(order_key)  COMMENT = 'Number of orders',
    orders.average_order_value AS AVG(order_total)  COMMENT = 'Average order total price'
)
COMMENT = 'TPC-H sales semantic view managed by dbt'

When you run dbt build, the Semantic View is created after the two staging tables. It is shown in the log as semantic_view model.

You can check the DDL that dbt issued in Query History.

When you query the Semantic View, it returns 5 rows with revenue and counts for each of the 5 segments.

SELECT *
FROM SEMANTIC_VIEW(
    DBT_SV_DB.DEV.SV_TPCH_SALES
    METRICS    orders.total_revenue, orders.order_count
    DIMENSIONS customers.market_segment
)
ORDER BY market_segment;

Reference it from a downstream model and look at the DAG

Add a downstream model that references the Semantic View with ref(). You just use ref() inside the semantic_view() function.

-- models/marts/revenue_by_segment_year.sql
select *
from semantic_view(
    {{ ref('sv_tpch_sales') }}
    metrics    orders.total_revenue, orders.order_count
    dimensions customers.market_segment, orders.order_year
)
order by market_segment, order_year

Add a materialization declaratively

https://docs.snowflake.com/en/release-notes/2026/10_24

Semantic View materializations became Public Preview on July 15, 2026. It is a mechanism that pre-aggregates frequently used combinations of dimensions and metrics. In plain SQL, you add them one at a time with ALTER SEMANTIC VIEW ... ADD MATERIALIZATION. With the package, you write the desired state in YAML and it is synchronized on every run.

https://docs.snowflake.com/en/user-guide/views-semantic/materializations

Change the top of sv_tpch_sales.sql as follows. To take the warehouse name from the target, the YAML is built with {% set %}, and create_or_alter=true and max_staleness are specified at the same time.

{%- set sv_mats_yaml -%}
materializations:
  - name: mat_revenue_by_segment_year
    warehouse: {{ target.warehouse }}
    dimensions:
      - table: customers
        name: market_segment
      - table: orders
        name: order_year
    metrics:
      - table: orders
        name: total_revenue
      - table: orders
        name: order_count
{%- endset -%}

{{ config(
    materialized='semantic_view',
    create_or_alter=true,
    max_staleness='1 hour',
    sv_materializations=sv_mats_yaml
) }}

TABLES (
    ...(本文は変更なし)

When you run dbt run --select sv_tpch_sales, dbt issues two statements in succession.
Looking at SHOW MATERIALIZATIONS IN SEMANTIC VIEW, there is one row for MAT_REVENUE_BY_SEGMENT_YEAR.

The state is ACTIVE, the warehouse is DBT_SV_WH, and the refresh_mode is FULL. According to the refresh_mode_reason column, the underlying object is a dynamic table, and AUTO was resolved to FULL because the aggregation spans two logical tables.

Differences between re-running, changing the definition, and CREATE OR REPLACE

Even when you run again with the same definition, the return value stays "Added/replaced: 1". However, no rebuild occurs. stale_by keeps increasing without interruption, and no FULL entries were added to the refresh history.

It is reasonable to read the number in the return value as the count written in the YAML, not as an indicator of whether anything changed. When I added orders.order_status to the YAML dimensions and ran, a FULL row was added to the refresh history 2 seconds after the run. stale_by also returned to 16s. Since nothing was added when I re-ran with the same definition, I can conclude it was rebuilt because of the definition change.

CREATE OR REPLACE drops the existing materialization. In this setup, the YAML synchronization right afterward recreates it, so every run incurs the compute cost of initialization (a full refresh). To avoid this, I assume create_or_alter=true.

Check the materialization state with generic tests

Write the package's bundled generic tests in models/semantic/schema.yml. The README example writes the arguments directly under the test name, but in dbt Core 1.12.3 this produces a deprecation warning, and in dbt Fusion it is an error (dbt1159). Put them under arguments:.

version: 2

models:
  - name: sv_tpch_sales
    tests:
      - dbt_semantic_view.materialization_exists:
          arguments:
            materialization_name: mat_revenue_by_segment_year
      - dbt_semantic_view.materialization_is_active:
          arguments:
            materialization_name: mat_revenue_by_segment_year

arguments: is the syntax for dbt Core 1.10.5 and later. With dbt 1.9.4, arguments is passed as an argument to the test macro, causing a compile error. The message is macro 'dbt_macro__test_materialization_exists' takes no keyword argument 'arguments'.

The two tests read the result of SHOW MATERIALIZATIONS. materialization_exists checks whether the name exists, and materialization_is_active checks that the state is not SUSPENDED. When you run dbt test, both PASS.

Next, I stop the materialization manually to see whether the tests detect it.

ALTER SEMANTIC VIEW DBT_SV_DB.DEV.SV_TPCH_SALES SUSPEND MATERIALIZATION MAT_REVENUE_BY_SEGMENT_YEAR;
SHOW MATERIALIZATIONS IN SEMANTIC VIEW DBT_SV_DB.DEV.SV_TPCH_SALES;

state becomes SUSPENDED, and suspend_reason becomes "The materialization was suspended by a user". When you run dbt test in this state, only materialization_is_active fails.

When you restore it with RESUME MATERIALIZATION, it returns to ACTIVE, and running dbt test again returns to 2 PASS. stale_by carries over the elapsed time from before the suspension, and no rebuild occurs.

ALTER SEMANTIC VIEW DBT_SV_DB.DEV.SV_TPCH_SALES RESUME MATERIALIZATION MAT_REVENUE_BY_SEGMENT_YEAR;

Running with dbt Fusion 2.0.0

dbt Projects on Snowflake also lets you choose dbt Fusion (2.x).
To state the conclusion first, you can create a Semantic View with Fusion as well. What you can't use is materialization management via create_or_alter and sv_materializations.

I run EXECUTE DBT PROJECT FROM WORKSPACE, which the Workspace execution uses internally, from a SQL file. Specify 2.0.0 with DBT_VERSION.

EXECUTE DBT PROJECT FROM WORKSPACE "USER$KAWABATA"."PUBLIC"."dbt_sv_demo"
  ARGS = 'parse'
  DBT_VERSION = '2.0.0';

The model with the materialization configs stopped at the parse stage with 2 errors. This matches what GitHub issue #34 reports.

Fusion validates configs strictly and treats unknown keys as errors. In dbt Core 1.12.3, the same keys only produced a CustomKeyInConfigDeprecation warning. Because the package reads the keys with config.get(), users can't work around it by moving them to meta.

On the other hand, the basic-form model whose config is only materialized='semantic_view' ran successfully with dbt run on Fusion 2.0.0 as well. Because the body is not a SELECT statement, a SyntaxInvalid (dbt0101) warning appears, but it doesn't affect execution.

Custom instructions can also be written in the body

The package passes the body to the DDL as-is. Therefore, any clause you can write in CREATE SEMANTIC VIEW can be used as is. The same goes for custom instructions (AI_SQL_GENERATION, AI_QUESTION_CATEGORIZATION). You can also write AI_VERIFIED_QUERIES and WITH SYNONYMS.

COMMENT = 'TPC-H sales semantic view managed by dbt'
AI_SQL_GENERATION 'If no time period is specified, filter to the last 7 days.'
AI_QUESTION_CATEGORIZATION 'Reject questions about individual customers.'

Limitations and notes

This covers the behavior as of November 11, 2026, and what is reported in the package's GitHub issues.

Limitations and notes
  • In dbt Fusion 2.0.0, create_or_alter and sv_materializations are rejected with UnusedConfigKey (dbt1060). This matches the report in issue #34, and the Fusion compatibility badge on the Hub is a mechanical check of parse. Basic-form Semantic View models also work on Fusion
  • External access is disabled by default on trial accounts (it can be enabled by requesting your account representative). In my environment, creating the External Access Integration failed with 509009. Either make the Hub package a local package, or run dbt deps locally from the CLI and bundle dbt_packages/
  • There are reports of introspection errors after creation on dbt Core 1.9.x (issue #5). They didn't occur on 1.12.3, but the trial default is 1.9.4, so specify the version explicitly
  • persist_docs is not supported (stated in the README). Write descriptions in the COMMENT clause of the DDL. The --empty flag also can't be used (issue #11)
  • There is a report that filter_clause in sv_materializations has no effect via the stored procedure (issue #35). refresh_mode is also ignored. If you need them, write ALTER SEMANTIC VIEW ... ADD MATERIALIZATION in a post_hook. However, the synchronization procedure deletes materializations that are not in the YAML. For that Semantic View, don't use sv_materializations and unify management on the post_hook side
  • The stored procedure's return value "Added/replaced: N" returns the number of entries in the YAML even when the definition is the same. Judge whether a rebuild occurred from stale_by or the refresh history
  • COPY GRANTS can't be used together with CREATE OR ALTER. CREATE OR ALTER requires OWNERSHIP of the Semantic View and can't change tags. Write generic test arguments under arguments:
  • Materializations are a preview feature, and they are effective only for queries that go through the Semantic View via SEMANTIC_VIEW() or standard SQL. They are not used for queries that issue SQL directly against the underlying physical tables, such as Cortex Analyst or Cortex Agent. While a materialization exists, you can't remove MAX_STALENESS

Conclusion

Because the dbt_semantic_view package is made only of Jinja macros, it worked as-is on dbt Core 1.12.3 in dbt Projects on Snowflake. The Semantic View and downstream models can be placed in the same DAG, and materializations can be synchronized idempotently with YAML. Creating a Semantic View also works on the Fusion runtime. The materialization management configs are stopped by validation, so if you want to use them, choosing dbt Core is the safe option for now.

I hope this article is helpful to someone!


Snowflakeの導入支援はクラスメソッドに!

クラスメソッドでは Snowflake の導入を支援しております。
製品の詳細や支援の内容についてお気軽にお問い合わせください。

Snowflakeの詳細を見る

Share this article