Snowflake Semantic Views vs dbt Semantic Layer: Where Metrics Live

Quick answer: A Snowflake semantic view is a schema-level object created with CREATE SEMANTIC VIEW, so it picks up Snowflake RBAC, tags, cloning, replication and sharing, and any SQL client can query it through SEMANTIC_VIEW(). The dbt Semantic Layer keeps metric definitions in version-controlled YAML with tests and CI, portable across warehouses, but serves them only through its own APIs. On snowflake semantic views vs the dbt semantic layer, most teams should run both: author in dbt, deploy as a semantic view.

Last updated: August 2026

Snowflake semantic views stopped being a Cortex Analyst implementation detail the moment they became a real schema object. CREATE SEMANTIC VIEW produces something that sits next to your tables, takes grants and tags, clones with the schema, replicates, shares, and answers plain SQL through SEMANTIC_VIEW(). That is a different animal from a YAML file parked in a stage.


If you already run metrics in dbt, this lands awkwardly. You have a MetricFlow spec under version control, tested in CI, reviewed in pull requests, and now the warehouse offers a competing home for the same definitions with better governance and worse engineering ergonomics. The instinct is to pick a winner. That is usually wrong.


This is the practical comparison: what each layer does, the gotchas, the cost trap in materializations, and the hybrid most teams with an existing dbt metrics layer should run. Every syntax and status claim is checked against Snowflake and dbt docs as of August 2026.


What does CREATE SEMANTIC VIEW actually create?


A semantic view is a schema-level object holding logical tables, their relationships, and three kinds of expression. It stores no data, only definitions and a join graph that Snowflake compiles into SQL over the physical tables.



Here is Snowflake's own TPC-H example. Read it closely: it shows the thing people get wrong first time, a fact feeding a metric rather than an aggregate defined twice.


SQL - the documented tpch_rev_analysis semantic view
CREATE SEMANTIC VIEW tpch_rev_analysis

  TABLES (
    orders AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS
      PRIMARY KEY (o_orderkey)
      WITH SYNONYMS ('sales orders')
      COMMENT = 'All orders table for the sales domain',
    customers AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
      PRIMARY KEY (c_custkey)
      COMMENT = 'Main table for customer data',
    line_items AS SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.LINEITEM
      PRIMARY KEY (l_orderkey, l_linenumber)
      COMMENT = 'Line items in orders'
  )

  RELATIONSHIPS (
    orders_to_customers AS
      orders (o_custkey) REFERENCES customers,
    line_item_to_orders AS
      line_items (l_orderkey) REFERENCES orders
  )

  FACTS (
    line_items.line_item_id AS CONCAT(l_orderkey, '-', l_linenumber),
    orders.count_line_items AS COUNT(line_items.line_item_id),
    line_items.discounted_price AS l_extendedprice * (1 - l_discount)
      COMMENT = 'Extended price after discount'
  )

  DIMENSIONS (
    customers.customer_name AS customers.c_name
      WITH SYNONYMS = ('customer name')
      COMMENT = 'Name of the customer',
    orders.order_date AS o_orderdate
      COMMENT = 'Date when the order was placed',
    orders.order_year AS YEAR(o_orderdate)
      COMMENT = 'Year when the order was placed'
  )

  METRICS (
    customers.customer_count AS COUNT(c_custkey)
      COMMENT = 'Count of number of customers',
    orders.order_average_value AS AVG(orders.o_totalprice)
      COMMENT = 'Average order value across all orders',
    orders.average_line_items_per_order AS AVG(orders.count_line_items)
      COMMENT = 'Average number of line items per order'
  )

  COMMENT = 'Semantic view for revenue analysis';

Notice orders.count_line_items: declared in FACTS as a COUNT over the related line items table, then averaged in METRICS. That is the shape to copy. Snowflake's docs describe facts as row-level attributes that usually act as helper concepts for building dimensions and metrics, and metrics as the aggregation across rows. A fact may roll up from a finer-grained table, as this one does, but the aggregation a business user asks for belongs in METRICS, defined once.


Clause order matters in the DDL too: the docs require FACTS before DIMENSIONS. Forward references are allowed, so a fact can refer to one declared below it.


How do you query a semantic view without an AI?


This is what changes the build-versus-buy calculation. A semantic view is not a Cortex-only artifact: two documented query forms exist, both plain SQL any JDBC or ODBC client can send.


SQL - both query forms against a semantic view
-- Form 1: the SEMANTIC_VIEW() construct in the FROM clause.
-- Clause order here is the output column order.
SELECT * FROM SEMANTIC_VIEW(
    tpch_rev_analysis
    DIMENSIONS orders.order_year
    METRICS orders.order_average_value
  )
  ORDER BY order_year;

-- Aliases work, with or without the AS keyword.
SELECT * FROM SEMANTIC_VIEW(
    tpch_rev_analysis
    DIMENSIONS orders.order_year AS yr
    METRICS orders.order_average_value avg_order
  )
  ORDER BY yr;

-- Form 2: name the semantic view directly in FROM.
-- Metrics must be wrapped in AGG(); dimensions must appear in GROUP BY.
SELECT order_year, AGG(order_average_value) AS avg_order
  FROM tpch_rev_analysis
  GROUP BY order_year
  ORDER BY order_year;

The second form is rewritten into the first: GROUP BY expressions become the DIMENSIONS clause, anything ungrouped becomes FACTS, and metrics go through the special AGG function. Passing a dimension or fact to any ordinary aggregate gives you an ad-hoc metric, handy for exploration but a bypass of the governed definition.


The privilege model is the quiet headline. Per the docs, querying a semantic view needs SELECT on the view itself, not on the tables it uses. That makes it a real privilege boundary: hand an analyst role the view and nothing else, and the only numbers they can reach are the ones you modelled.


The friendlier name-in-FROM form has hard limits: no joins, no subqueries, no window function calls, no QUALIFY, and none of the FROM-clause extensions PIVOT, UNPIVOT, MATCH_RECOGNIZE or LATERAL. If your BI tool generates any of those, it needs SEMANTIC_VIEW() wrapped in a derived table instead.


What a semantic view gives you that dbt metrics do not


None of this is about SQL generation quality. It is that the object lives in the database, dragging the whole Snowflake governance surface with it.



SQL - grants and tagging on a semantic view
-- The semantic view is the privilege boundary. Analysts get SELECT on the
-- view and nothing on ORDERS, CUSTOMER or LINEITEM.
GRANT SELECT ON SEMANTIC VIEW tpch_rev_analysis TO ROLE analyst_role;

-- Cortex Analyst needs REFERENCES as well as SELECT when the role
-- does not own the view.
GRANT REFERENCES, SELECT ON SEMANTIC VIEW tpch_rev_analysis
  TO ROLE my_analyst_role;

-- Cover everything the modelling team ships into the schema from now on.
GRANT REFERENCES, SELECT ON FUTURE SEMANTIC VIEWS IN SCHEMA my_schema
  TO ROLE my_analyst_role;

-- ALTER only sets tags at the view level.
ALTER SEMANTIC VIEW tpch_rev_analysis
  SET TAG governance.certification = 'gold';

One wrinkle worth knowing before you design a tagging standard: ALTER SEMANTIC VIEW only sets and unsets tags at the semantic view level. Tags on logical tables, facts, dimensions and metrics have to be set through CREATE SEMANTIC VIEW, and CREATE OR ALTER SEMANTIC VIEW preserves existing tags rather than changing them.


What dbt still owns that semantic views cannot


The temptation after that list is to declare dbt metrics redundant. Mistake. A semantic view is good at runtime; dbt is good before deployment.



One commercial detail: defining and querying metrics with the dbt Semantic Layer requires a paid dbt platform plan (Starter, Enterprise or Enterprise+). MetricFlow is open source; the served layer is not.


Snowflake semantic views vs dbt Semantic Layer: side by side


CapabilitySnowflake semantic viewdbt Semantic Layer
Where the definition livesSchema object in the databaseYAML in a Git repository
Access controlSnowflake RBAC; SELECT on the view sufficesdbt platform permissions plus warehouse credentials
Query from plain SQLYes, via SEMANTIC_VIEW() or the view nameNo, only via the Semantic Layer APIs
TaggingTAG on view, tables, facts, dimensions, metricsmeta and tags in YAML, not warehouse tags
LineageObject-level only; no column lineageFull ref graph across the project
PortabilitySnowflake onlySnowflake, BigQuery, Databricks, Redshift, Postgres, Trino
External sharingListings, Marketplace, data sharingNot a warehouse-level share
Pre-aggregationMaterializations (preview)Handled upstream in dbt models
Tests in CIBuild it yourselfNative dbt tests
AI consumptionCortex Analyst and Agents read it directlyThrough the Semantic Layer APIs
LicensingIncluded with SnowflakePaid dbt plan required

The gotchas that bite in the first hour


Four documented restrictions cause most of the early frustration. None are bugs; all surprise you once.



SQL - the granularity error and how to avoid guessing
-- Snowflake's documented tpch_analysis view. This query FAILS because the
-- dimension's table is finer-grained than the metric's table.
SELECT * FROM SEMANTIC_VIEW (
  tpch_analysis
  DIMENSIONS orders.order_date
  METRICS customer.customer_order_count
);

-- 010234 (42601): SQL compilation error:
-- Invalid dimension specified: The dimension entity 'ORDERS' must be related to and
--   have an equal or lower level of granularity compared to the base metric or
--   dimension entity 'CUSTOMER'.

-- Ask Snowflake which dimensions are legal for that metric before you guess.
SHOW SEMANTIC DIMENSIONS IN tpch_analysis FOR METRIC customer_order_count;

That last one is the most common question we field after a semantic view goes live. Run SHOW SEMANTIC DIMENSIONS ... FOR METRIC while you model, and put the resulting list in the metric's COMMENT so nobody rediscovers it later.


One more: in the name-in-FROM form, a qualified name like nation.name is read as the view name, not the logical table, so two logical tables exposing a dimension called name break it. Name dimensions uniquely from the start.


Materializations: real speedup, real cost trap


Snowflake documents materializing dimensions and metrics as an open preview feature available to all accounts. It pre-computes selected expressions so queries read stored results instead of rescanning base tables. Check its status before you build a plan around it.


SQL - adding a materialization to a semantic view
-- MAX_STALENESS must exist before any materialization can be added.
-- Minimum is 120 seconds.
ALTER SEMANTIC VIEW revenue_analysis SET MAX_STALENESS = '1 hour';

ALTER SEMANTIC VIEW revenue_analysis ADD MATERIALIZATION revenue_by_year
  WAREHOUSE = transform_wh
  REFRESH_MODE = INCREMENTAL
  IMMUTABLE WHERE ( order_year < 2026 )
AS
  DIMENSIONS order_year, customer_name
  METRICS total_revenue, order_count;

Here is the trap, documented plainly enough that nobody should be caught by it: materializations only benefit queries executed as Semantic SQL, meaning the SEMANTIC_VIEW construct or standard SQL against the view. Cortex Analyst, Cortex Agents and Snowflake CoWork issue physical SQL directly against the underlying tables and get no benefit at all.


So if you bought semantic views mainly for natural-language querying, materializations burn refresh credits and speed up nothing your users touch. They pay off when BI tools hit the view directly.



The hybrid: author in dbt, deploy as a semantic view


This does not have to be a fight, because the semantic view is a passthrough target. Snowflake Labs publishes a dbt_semantic_view package on dbt Hub adding a semantic_view materialization, so a semantic view is just another dbt model with a ref-aware definition.


dbt - defining a semantic view as a dbt model
# packages.yml - check hub.getdbt.com for the current version
packages:
  - package: Snowflake-Labs/dbt_semantic_view
    version: 1.0.5

-- models/semantic/rev_analysis.sql
{{ config(materialized='semantic_view') }}

TABLES(
  orders AS {{ ref('fct_orders') }} PRIMARY KEY (order_id),
  customers AS {{ ref('dim_customers') }} PRIMARY KEY (customer_id)
)
RELATIONSHIPS(
  orders_to_customers AS orders (customer_id) REFERENCES customers
)
FACTS(
  orders.net_amount AS gross_amount - discount_amount
)
DIMENSIONS(
  orders.order_year AS YEAR(order_date),
  customers.segment AS segment
)
METRICS(
  orders.total_revenue AS SUM(orders.net_amount),
  orders.order_count AS COUNT(orders.order_id)
)
COMMENT='Revenue metrics, generated from dbt models'

Snowflake's docs describe the package as a direct passthrough to Snowflake's SQL layer, so new semantic view syntax is usable immediately without waiting for a package release. The tradeoff is ownership: it is an Apache 2.0 community package from the Snowflake Labs organisation rather than part of the Snowflake product, and dbt Hub states plainly that dbt Labs does not certify any package. Treat it as a documented pattern, not a supported product.


That split matches how the two tools are built. dbt owns models, tests and the deployment pipeline. The semantic view owns runtime governance, grants, tagging, sharing and AI consumption. One source of truth in Git, one enforced boundary in the database.


For redeploys use CREATE OR ALTER SEMANTIC VIEW, not CREATE OR REPLACE. It preserves existing grants without COPY GRANTS, and leaves the object unchanged if the definition already matches. That matters the first time a CI pipeline runs at 3am and quietly drops every analyst grant.


Nobody wants to hand-write that DDL for 400 tables


This objection deserves a straight answer. The TPC-H example is 40 lines for three tables; a mid-size warehouse has hundreds. Hand-authoring across all of them is not a project anyone should scope.


Snowflake's answer is Semantic View Autopilot, an AI-assisted generator in Snowsight. Feed it a description, a data source, and context as example SQL, table metadata or an exported BI model. It validates the SQL, discards invalid queries, extracts tables and relationships for review, infers keys from cardinality, and adds valid queries as verified queries. Tableau ingestion is documented; Power BI file ingestion (.pbit or .pbix) is a preview feature.


But the honest scoping answer is a decision, not a tool. You do not model 400 tables. You model the ten to fifteen metrics executives argue about and the star they hang off: revenue recognition, active customers, churn, margin, pipeline. The rest stays as tables and views analysts query normally.


Scoped that way a first semantic view is two to three weeks, not a quarter: pick the metric set, confirm each logical table's grain, write the relationships, draft with Autopilot, then spend the rest on review and on the synonyms and comments that make natural-language querying work. Agreeing what "active customer" means is the slow part, and no tool removes it.


How to decide, in four rules



The framing that holds up across our projects: dbt is where metrics get written and reviewed, the semantic view is where they get enforced and served. Pick one and you rebuild the other half badly.


Pranay Vatsal, Founder & CEO

Pranay Vatsal is the Founder & CEO of CelestInfo with deep expertise in Snowflake, data architecture, and building production-grade data systems for global enterprises.

Related Articles

Frequently Asked Questions

Q: Do I need dbt metrics if I have Snowflake semantic views?

Not for serving metrics inside Snowflake, but you lose what dbt does before deployment: version control, pull request review, tests and CI. Snowflake's own guidance is to keep the semantic view YAML or DDL in Git and deploy it from a pipeline, which describes dbt. Keep dbt as the authoring layer.

Q: Can I query a Snowflake semantic view from Power BI or Tableau?

Any client that can send SQL can query one through SEMANTIC_VIEW(). Snowflake also documents SYSTEM$EXPORT_TDS_FROM_SEMANTIC_VIEW to produce a Tableau Data Source file, currently a preview feature, and lists integrations from Sigma, Omni, Honeydew, Hex and ThoughtSpot. For Power BI the documented direction runs the other way: Autopilot ingests a .pbit or .pbix to generate a view.

Q: Is the stage-hosted YAML semantic model file still supported for Cortex Analyst?

Yes, it still works, but Snowflake explicitly recommends semantic views instead because semantic models are YAML files in a stage and lack native database integration. Semantic views support derived metrics, private expressions, the privilege system, sharing and the metadata catalog. Existing YAML models can be converted with the SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML stored procedure.

Q: Why does my semantic view query fail with an error about level of granularity?

Error 010234 means the dimension sits on a logical table that is finer-grained than the metric's table, or is not related to it. A dimension's table must be related to the metric's table and have an equal or lower level of granularity. Run SHOW SEMANTIC DIMENSIONS ... FOR METRIC to list the valid dimensions.

Q: Do semantic view materializations make Cortex Analyst faster?

No. Snowflake documents that materializations benefit queries executed as Semantic SQL, meaning the SEMANTIC_VIEW construct or standard SQL against the view. Cortex Analyst, Cortex Agents and Snowflake CoWork issue physical SQL directly against the underlying tables and bypass materializations entirely. If natural-language querying is your main workload, materializations cost credits and return nothing.

Q: Can I share a semantic view with another Snowflake account?

Yes. Semantic views are in Snowflake's list of shareable objects and can be shared through private listings, organizational listings and the Marketplace. Two details catch people out. First, the semantic view references underlying tables, so you also grant REFERENCES and SELECT on the view to the share and SELECT on each table it uses. Run DESCRIBE SEMANTIC VIEW to find them. Second, for cross-account replication you must replicate the referenced objects too, or the view will not function in the target account. Semantic views also clone when the schema containing them is cloned.