# From dbt Models to Snowflake Semantic Views: Best Practices for Cortex Analyst

Most teams I talk to already have a decent dbt project and are now looking at **Semantic Views** and **Cortex Analyst**. The question is usually:

> “How do I reuse what we have in dbt instead of building a second semantic layer from scratch?”

The good news: you don’t have to choose. dbt can stay your **transformation + modeling** workhorse, and Semantic Views can become the **thin semantic layer** that powers Cortex Analyst (and other consumers) on top.

In this post I’ll show my favorite pattern that work well in practice:

*   Define Semantic Views **inside** dbt with a dedicated package.
    

Along the way I’ll share a few design tips that made Cortex Analyst answers a lot more predictable.

* * *

## Why add Semantic Views on top of dbt?

dbt is great at turning raw data into clean **dim/fact models** and shared SQL logic.

Semantic Views pick up where dbt stops:

*   They describe **business concepts** (dimensions, time dimensions, metrics, relationships).
    
*   They live as first‑class Snowflake objects (`SEMANTIC VIEW`) with proper governance and sharing.
    
*   They’re what **Cortex Analyst** uses to translate natural language into SQL.
    

A simple way to think about it:

*   dbt: *“What does the warehouse look like?”*
    
*   Semantic Views: *“How does the business talk about this data?”*
    

* * *

## Define Semantic Views in dbt (`Snowflake-Labs/dbt_semantic_view`)

If you already treat dbt as your system of record, this is the most natural option.

1.  Add the package:
    

```yaml
packages:
  - package: Snowflake-Labs/dbt_semantic_view
    version: 1.0.3
```

2.  Create a model that uses the `semantic_view` materialization and points at your existing models:
    

```sql
{{ config(materialized = 'semantic_view', schema = 'SEMANTICS') }}

semantic_view:
  name: orders_analytics_sv

  tables:
    - name: dim_customers
      base_table: {{ ref('dim_customers') }}
      primary_key: customer_id

    - name: fct_orders
      base_table: {{ ref('fct_orders') }}
      primary_key: order_id
      time_dimensions:
        - name: order_date
          expr: order_date

  metrics:
    - name: total_revenue
      expr: "SUM(fct_orders.order_amount)"
```

3.  Run it like any other dbt model:
    

```bash
dbt run --select orders_analytics_sv
```

Behind the scenes this compiles to a `CREATE SEMANTIC VIEW` statement. You get:

*   A managed Semantic View in Snowflake.
    
*   Version control and reviews via dbt.
    
*   The same deployment story as the rest of your project.
    

This is ideal when you want **one place** (dbt) to define how tables, relationships, and metrics are wired.

* * *

## Making Cortex Analyst happy

Regardless of how you create Semantic Views, a few simple rules go a long way:

*   **Keep them focused.** Think in domains like “Orders & Customers” or “Account Usage”, not “everything in the warehouse”.
    
*   **Model real business language.** Use dimensions and metrics that match how people actually ask questions (“revenue”, “active customers”, “region”). Add synonyms if your org loves acronyms.
    
*   **Wire relationships explicitly.** Many‑to‑one from facts to dimensions on clean keys avoids a lot of weird joins.
    
*   **Start with a handful of verified questions.** For your first Semantic View, capture 5–10 real questions and the exact SQL you expect. Use those as guardrails when you iterate. (as of writing in Private Preview)
    

With that in place, Cortex Analyst has a much easier time turning “show me revenue by customer segment for last quarter” into the SQL you would have written yourself.

* * *

## A pragmatic migration path

If I had to start from scratch on an existing dbt project, I’d do this:

1.  Pick one high‑value domain (e.g. product analytics, finance, account usage).
    
2.  Create a **single** Semantic View for that domain using either option above.
    
3.  Add 5–15 metrics that matter and a small set of verified questions.
    
4.  Put it in front of real users, see which questions fail, and iterate.
    

Once that loop feels smooth, repeat for the next domain.

You end up with a thin, governed semantic layer on top of dbt that unlocks natural language and other consumers—without throwing away the modeling work you already invested in.

* * *

## Bonus: experimental dbt package for converting dbt semantic models to Semantic Views

One thing I ran into while working on this: a lot of teams are already investing in **dbt’s semantic layer** (semantic models, measures, entities), but would still like to end up with **Snowflake-native Semantic Views** at the end of the day.

Instead of rewriting everything by hand, I started an **experimental** dbt package that tries to bridge exactly that gap:

> [`anthu/dbt_semantic_view_converter`](https://github.com/anthu/dbt_semantic_view_converter) – very early, APIs may change, feedback highly welcome.

The idea is simple:

*   You define **semantic models** in dbt the way you normally would (in `schema.yml`).
    
*   You add a small **semantic view model** with a special materialization.
    
*   When you run `dbt run`, the package generates the corresponding `CREATE SEMANTIC VIEW` DDL for you and creates a Snowflake Semantic View based on that config.
    

### How it works (high level)

1.  **Install the package** in `packages.yml`:
    

```yaml
packages:
  - git: "https://github.com/sfc-gh-ahuck/dbt_semantic_view_converter.git"
    revision: main
```

Then:

```bash
dbt deps
dbt parse
```

2.  **Define your semantic model** (dbt semantic layer) in `schema.yml`:
    

```yaml
semantic_models:
  - name: orders
    description: "Order fact table"
    model: ref('dim_orders')

    entities:
      - name: order_id
        type: primary
      - name: customer_id
        type: foreign

    dimensions:
      - name: order_date
        type: time
        type_params:
          time_granularity: day
      - name: order_status
        type: categorical

    measures:
      - name: order_total
        agg: sum
      - name: order_count
        expr: 1
        agg: sum
```

3.  **Create a corresponding “semantic view” model** in dbt:
    

```sql
-- models/semantic_views/orders_semantic_view.sql

{{ config(
    materialized = 'semantic_view',
    schema = 'semantic_layer'
) }}

-- The SELECT itself is just a placeholder.
-- The package reads the semantic model config and generates the DDL.
SELECT 1 AS placeholder;
```

4.  **Run dbt**:
    

```bash
dbt run --models orders_semantic_view
```

Behind the scenes, the package inspects your semantic model config and emits a `CREATE OR REPLACE SEMANTIC VIEW` statement with tables, relationships, dimensions, facts, and metrics wired up according to that definition.

The end result looks roughly like:

```sql
CREATE OR REPLACE SEMANTIC VIEW analytics.semantic_layer.orders
  COMMENT = 'Order fact table'
  /* TABLES, RELATIONSHIPS, DIMENSIONS, METRICS ... */
  COPY GRANTS;
```

### Why this might be interesting

If you’re already invested in dbt’s semantic layer, this gives you a path to:

*   Keep **one definition of business logic** (semantic models in dbt),
    
*   But still end up with **Snowflake-native Semantic Views** that Cortex Analyst and other tools can consume directly,
    
*   While reusing dbt’s existing workflows for **dependencies, tests, docs, and CI/CD**.
    

It’s especially handy for teams who:

*   Don’t want to maintain two separate semantic definitions,
    
*   Prefer reviewing semantic changes via PRs in the dbt repo,
    
*   And like the idea of “dbt is the source of truth, Snowflake Semantic Views are the runtime interface”.
    

### Please treat it as experimental

This is very much a **work-in-progress**:

*   The materialization name, config options, and generated SQL shape may still change.
    
*   Error messages and guardrails are basic.
    
*   I’m still figuring out what the “right” abstraction level is (how much of the Snowflake DDL to expose vs hide).
    

So: **don’t** drop it straight into mission‑critical projects yet.

If you do try it out in a sandbox or side‑project, I’d really love feedback:

*   Does the workflow fit how you use dbt today?
    
*   Is the mapping from semantic models → Semantic View what you expect?
    
*   What would you need before trusting this in a real environment?
    

Issues, discussions, and PRs are all welcome in the repo:

👉 [`anthu/dbt_semantic_view_converter` on GitHub](https://github.com/anthu/dbt_semantic_view_converter)

* * *

## Further reading

*   [Best practices for semantic views (Snowflake Docs)](https://docs.snowflake.com/en/user-guide/views-semantic/best-practices-dev)
    
*   [Using Snowsight to create and manage semantic views](https://docs.snowflake.com/en/user-guide/ui-snowsight-data-databases-view)e semantic views
    
*   [`dbt_semantic_view` package on dbt Hub](https://github.com/Snowflake-Labs/dbt_semantic_view)
    
*   [GitHub - anthu/dbt\_semantic\_view\_converter · GitHub](https://github.com/anthu/dbt_semantic_view_converter)
