Complete reference for creating semantic models with Snowflake Semantic Tools.
Semantic models provide a business-friendly layer on top of your dbt models, defining:
- Metrics: Aggregated business calculations (revenue, counts, averages)
- Relationships: How tables join together via foreign keys
- Filters: Reusable WHERE clause conditions
- Custom Instructions: Business rules for Cortex Analyst
- Verified Queries: Validated example queries for AI training
- Semantic Views: Curated combinations of the above
All semantic models use templates that reference your dbt models:
| Template | Purpose | Example |
|---|---|---|
{{ ref('name') }} |
Reference a dbt model (unified syntax - recommended) | {{ ref('orders') }} |
{{ ref('table', 'col') }} |
Reference a specific column (unified syntax - recommended) | {{ ref('orders', 'customer_id') }} |
{{ table('name') }} |
Reference a dbt model (legacy - still supported) | {{ table('orders') }} |
{{ column('table', 'col') }} |
Reference a specific column (legacy - still supported) | {{ column('orders', 'customer_id') }} |
{{ metric('name') }} |
Reference another metric | {{ metric('total_revenue') }} |
{{ custom_instructions('name') }} |
Apply business rules | {{ custom_instructions('exclude_test_users') }} |
Why Templates?
- Validated: References are checked against your dbt catalog
- Portable: Work across environments (dev/staging/prod)
- Maintainable: Updates automatically when dbt models change
Your semantic models live in snowflake_semantic_models/ by default:
snowflake_semantic_models/
├── metrics/
│ ├── sales.yml
│ ├── customers.yml
│ └── finance.yml
├── relationships/
│ ├── core.yml
│ └── sales.yml
├── filters/
│ └── filters.yml
├── custom_instructions/
│ └── custom_instructions.yml
├── verified_queries/
│ └── verified_queries.yml
└── semantic_views.yml
Note: The directory path is configurable via
project.semantic_models_dirinsst_config.yml.
Files are discovered recursively - organize however makes sense for your domain.
Your dbt model YAML files need SST metadata in the config.meta.sst section. This is the dbt Fusion-compatible format.
Note: If you have existing models using the legacy
meta.sstformat, runsst migrate-metato convert them. See the dbt Fusion Migration Guide.
models:
- name: orders
description: Order fact table with one row per order
config:
meta:
sst:
primary_key: order_id # Required: unique identifier
unique_keys: [customer_id, ordered_at] # Optional: for ASOF relationships
synonyms: # Optional: alternative names
- purchases
- transactionsFor range-based relationships (e.g., date ranges that don't overlap), declare constraints at the table level:
models:
- name: exchange_rates
description: Exchange rate periods with non-overlapping date ranges
config:
meta:
sst:
primary_key: rate_id
constraints:
- type: distinct_range
name: rate_date_range
start_column: effective_start
end_column: effective_endThis generates:
CONSTRAINT RATE_DATE_RANGE DISTINCT RANGE
BETWEEN EFFECTIVE_START AND EFFECTIVE_END EXCLUSIVEApply Snowflake governance tags at the table or column level:
models:
- name: customers
config:
meta:
sst:
primary_key: customer_id
tags:
GOVERNANCE.SCHEMA.PII_LEVEL: "high"
GOVERNANCE.SCHEMA.DATA_DOMAIN: "customer"
columns:
- name: email
config:
meta:
sst:
column_type: dimension
data_type: TEXT
tags:
GOVERNANCE.SCHEMA.PII_TYPE: "email"Note: Tag names should be fully-qualified Snowflake tag objects (
DB.SCHEMA.TAG_NAME). Unqualified names produce a validation warning.
| Field | Required | Purpose |
|---|---|---|
primary_key |
Yes | Column(s) that uniquely identify each row. Used in semantic view PRIMARY KEY clause. |
unique_keys |
No | Column(s) forming a unique constraint. Required for ASOF relationships - Snowflake needs this to validate temporal joins. |
constraints |
No | List of constraint objects. Currently supports distinct_range type for non-overlapping range declarations. |
tags |
No | Dict of tag_name: tag_value pairs. Tag names should be fully-qualified (e.g., DB.SCHEMA.TAG_NAME). |
synonyms |
No | Alternative names users might use to refer to this table |
columns:
- name: order_id
description: Unique order identifier
config:
meta:
sst:
column_type: dimension # Required: dimension, fact, or time_dimension
data_type: TEXT # Required: Snowflake data type
synonyms: [] # Optional: alternative column names
sample_values: # Optional: example values for AI context
- "ORD-001"
- "ORD-002"
is_enum: false # Optional: true if sample_values is exhaustive
exclude: false # Optional: true to hide column from semantic viewAdd exclude: true to any column's config.meta.sst to hide it from the semantic view entirely:
columns:
- name: email_address
description: Customer email (PII)
config:
meta:
sst:
exclude: true # Column will not appear in the semantic view
- name: customer_id
config:
meta:
sst:
column_type: dimension
data_type: TEXTExcluded columns are:
- Skipped during enrichment — no sample values, synonyms, or type detection
- Omitted from extraction — not written to SM_DIMENSIONS/SM_FACTS/SM_TIME_DIMENSIONS
- Absent from generated DDL — do not appear in DIMENSIONS or FACTS clauses
- Skipped in validation — no
column_typerequirement
Use cases:
- PII columns on joined tables that shouldn't be queryable via Cortex Agents
- Internal/technical columns (ETL timestamps, hash keys, debug flags)
- Columns that exist in the warehouse but aren't relevant to the semantic layer
Derived metrics combine metrics from multiple entities without belonging to a specific table:
snowflake_metrics:
- name: revenue_to_customer_ratio
derived: true
description: Revenue divided by customer count
expr: "{{ metric('total_revenue') }} / NULLIF({{ metric('total_customers') }}, 0)"Derived metrics:
- Use
{{ metric('name') }}references (resolved toTABLE.METRIC_NAMEin DDL) - Are emitted without a table prefix in the semantic view
- Cannot use
using_relationships,non_additive_by, orwindow - Don't require a
tablesfield (inferred from referenced metrics)
The tables field is optional. SST auto-infers table references from the expression:
snowflake_metrics:
# tables auto-inferred from expr → ['orders']
- name: total_revenue
expr: "SUM({{ ref('orders', 'order_total') }})"Inference strategies (in priority order):
{{ ref('table', 'col') }}and{{ column('table', 'col') }}patternsTABLE.COLUMNdot-references (post-template-resolution)- For derived metrics: resolved transitively from referenced metrics
Explicit tables always takes precedence when provided.
When defining ASOF (temporal) relationships, Snowflake requires a UNIQUE constraint on the columns involved in the join:
# In your dbt model YAML
models:
- name: orders
config:
meta:
sst:
primary_key: order_id
unique_keys: [customer_id, ordered_at] # Columns used in ASOF join
# In your relationship definition (using unified ref() syntax)
snowflake_relationships:
- name: orders_to_prior_orders
left_table: {{ ref('orders') }}
right_table: {{ ref('orders') }}
relationship_conditions:
- "{{ ref('orders', 'customer_id') }} = {{ ref('orders', 'customer_id') }}"
- "{{ ref('orders', 'ordered_at') }} >= {{ ref('orders', 'ordered_at') }}"This generates:
ORDERS AS SCHEMA.ORDERS
PRIMARY KEY (ORDER_ID)
UNIQUE (CUSTOMER_ID, ORDERED_AT)Metrics define aggregated business calculations.
snowflake_metrics:
- name: metric_name
tables:
- {{ ref('table_name') }} # Unified syntax (legacy {{ table('table_name') }} also works)
description: Clear description of what this measures
expr: SQL expression using aggregate functions (e.g., SUM({{ ref('table_name', 'column_name') }}))
synonyms:
- alternative_name_1
- alternative_name_2
sample_values:
- 12345.67
- 23456.78name: Unique identifier (becomes uppercase in Snowflake)tables: List of dbt tables this metric referencesexpr: SQL expression with aggregate functiondescription: Business explanation
synonyms: Alternative names users might usesample_values: Example values for AI contextvisibility:"private"or"public"(default). Private metrics are hidden from end users in Cortex Analyst.non_additive_by: Semi-additive configuration for measures that cannot be summed across time (e.g., balances, inventory, headcount). See Semi-Additive Metrics.using_relationships: List of relationship names for join path disambiguation when multiple relationships exist between tables. See Join Path Disambiguation.window: Window function configuration for running totals, cumulative sums, rankings, and moving averages. See Window Function Metrics.tags: Dict oftag_name: tag_valuepairs. Tag names should be fully-qualified Snowflake tag objects (e.g.,DB.SCHEMA.TAG_NAME).
For measures that cannot be summed across time dimensions (e.g., account balances, inventory levels):
snowflake_metrics:
- name: current_balance
tables:
- {{ ref('account_snapshots') }}
description: Current account balance (semi-additive — use latest value, not sum)
expr: MAX({{ ref('account_snapshots', 'balance') }})
non_additive_by:
- dimension: snapshot_date
order: DESC
nulls: LASTFields within non_additive_by:
| Field | Required | Values | Description |
|---|---|---|---|
dimension |
Yes | Column name | The time dimension this metric is non-additive over |
order |
No | ASC / DESC |
Sort direction (default: ASC) |
nulls |
No | FIRST / LAST |
NULL sort position |
This generates:
ACCOUNT_SNAPSHOTS.CURRENT_BALANCE
NON ADDITIVE BY (SNAPSHOT_DATE DESC NULLS LAST) AS MAX(ACCOUNT_SNAPSHOTS.BALANCE)
When multiple relationships exist between the same tables, use using_relationships to specify which join path a metric should use:
snowflake_metrics:
- name: shipping_revenue
tables:
- {{ ref('orders') }}
description: Revenue attributed via shipping relationship
expr: SUM({{ ref('orders', 'shipping_amount') }})
using_relationships:
- orders_to_shipping_addressThis generates:
ORDERS.SHIPPING_REVENUE
USING (ORDERS_TO_SHIPPING_ADDRESS) AS SUM(ORDERS.SHIPPING_AMOUNT)
For running totals, cumulative sums, rankings, and moving averages:
snowflake_metrics:
- name: running_total_revenue
tables:
- {{ ref('orders') }}
description: Cumulative revenue partitioned by region
expr: SUM({{ ref('orders', 'amount') }})
window:
partition_by:
- {{ ref('orders', 'region') }}
order_by:
- column: {{ ref('orders', 'order_date') }}
direction: ASCWindow configuration fields:
| Field | Description |
|---|---|
partition_by |
List of columns to partition by |
partition_by_excluding |
List of columns to exclude from partitioning (Snowflake-specific, mutually exclusive with partition_by) |
order_by |
List of order specifications (objects with column + direction, or bare column strings) |
This generates:
ORDERS.RUNNING_TOTAL_REVENUE AS SUM(ORDERS.AMOUNT) OVER (
PARTITION BY ORDERS.REGION ORDER BY ORDERS.ORDER_DATE ASC
)
Mark metrics or facts as private to hide them from end users while keeping them available for metric composition:
snowflake_metrics:
- name: internal_discount_rate
tables:
- {{ ref('pricing') }}
description: Internal discount calculation — not for external use
expr: SUM({{ ref('pricing', 'internal_rate') }})
visibility: privateNote: Only facts and metrics support visibility. Dimensions and time dimensions cannot be marked private (Snowflake restriction).
snowflake_metrics:
- name: total_order_count
tables:
- {{ ref('orders') }} # Unified syntax (legacy {{ table('orders') }} also works)
description: Total number of orders placed
expr: |
COUNT(DISTINCT {{ ref('orders', 'order_id') }}) # Unified syntax (legacy {{ column('orders', 'order_id') }} also works)
synonyms:
- total_orders
- order_count
- name: average_rating
tables:
- {{ ref('product_reviews') }}
description: Average product rating (1-5 stars) based on customer reviews
expr: |
AVG({{ ref('product_reviews', 'rating') }})
synonyms:
- avg_rating
- mean_rating
- name: conversion_rate
tables:
- {{ table('user_funnel') }}
description: Conversion rate from visitors to customers
expr: |
{{ metric('completed_purchases') }} /
NULLIF({{ metric('total_visitors') }}, 0)
synonyms:
- purchase_conversion_rateReference other metrics in expressions:
snowflake_metrics:
- name: total_orders
tables:
- {{ table('orders') }}
description: Total number of orders
expr: COUNT(*)
- name: total_revenue
tables:
- {{ table('orders') }}
description: Sum of all order amounts
expr: SUM({{ column('orders', 'amount') }})
- name: average_order_value
tables:
- {{ table('orders') }}
description: Average revenue per order
expr: {{ metric('total_revenue') }} / NULLIF({{ metric('total_orders') }}, 0)Define how tables join together.
snowflake_relationships:
- name: descriptive_relationship_name
left_table: {{ table('source_table') }}
right_table: {{ table('target_table') }}
relationship_conditions:
- "{{ column('source_table', 'join_key') }} = {{ column('target_table', 'join_key') }}"name: Descriptive relationship identifierleft_table: Source table template (contains foreign key)right_table: Target table template (contains primary key)relationship_conditions: Array of join conditions with operators
Important: Each condition must be quoted (double quotes) because {{ has special meaning in YAML. Without quotes, YAML will fail to parse the file.
snowflake_relationships:
- name: orders_to_customers_by_date
left_table: {{ table('orders') }}
right_table: {{ table('customers') }}
relationship_conditions:
- "{{ column('orders', 'customer_id') }} = {{ column('customers', 'customer_id') }}"
- "{{ column('orders', 'order_date') }} = {{ column('customers', 'registration_date') }}"For time-series data, use the >= operator for ASOF joins:
snowflake_relationships:
- name: events_to_sessions
left_table: {{ table('user_events') }}
right_table: {{ table('user_sessions') }}
relationship_conditions:
- "{{ column('user_events', 'session_id') }} = {{ column('user_sessions', 'session_id') }}"
- "{{ column('user_events', 'event_time') }} >= {{ column('user_sessions', 'start_time') }}"Supported operators:
=- Equality joins>=- ASOF (temporal) joinsBETWEEN...AND...EXCLUSIVE- Range joins (requires CONSTRAINT DISTINCT RANGE on the right table)
For range-based relationships where a value falls within a non-overlapping range (e.g., mapping a transaction date to an exchange rate period):
snowflake_relationships:
- name: orders_to_rates
left_table: {{ ref('orders') }}
right_table: {{ ref('exchange_rates') }}
relationship_conditions:
- "{{ ref('orders', 'ordered_at') }} BETWEEN {{ ref('exchange_rates', 'effective_start') }} AND {{ ref('exchange_rates', 'effective_end') }} EXCLUSIVE"This generates:
ORDERS (ORDERED_AT) REFERENCES EXCHANGE_RATES (BETWEEN EFFECTIVE_START AND EFFECTIVE_END EXCLUSIVE)Prerequisite: The right table must have a
CONSTRAINT DISTINCT RANGEdeclared (see Constraints) to guarantee non-overlapping ranges.
When joining tables on transformed columns (e.g., a TIMESTAMP to a DATE), you can use SQL expressions directly in relationship conditions. SST auto-generates a synthetic "join key dimension" behind the scenes so the relationship works in Snowflake's semantic view.
snowflake_relationships:
- name: orders_to_calendar
left_table: {{ ref('orders') }}
right_table: {{ ref('calendar') }}
relationship_conditions:
- "DATE({{ ref('orders', 'ordered_at') }}) = {{ ref('calendar', 'date_day') }}"This generates a hidden dimension and references it in the relationship:
DIMENSIONS (
...
ORDERS._JK_ORDERED_AT_032F AS DATE(ORDERED_AT)
COMMENT = 'Auto-generated join key: DATE(ORDERED_AT)'
)
RELATIONSHIPS (
ORDERS_TO_CALENDAR AS
ORDERS (_JK_ORDERED_AT_032F) REFERENCES CALENDAR (DATE_DAY)
)Supported expression patterns:
Any valid Snowflake SQL expression that wraps a single column reference is supported. Common examples:
| Pattern | Example |
|---|---|
DATE() |
DATE({{ ref('orders', 'ordered_at') }}) |
DATE_TRUNC() |
DATE_TRUNC('month', {{ ref('orders', 'ordered_at') }}) |
:: cast |
{{ ref('orders', 'ordered_at') }}::DATE |
CAST(... AS ...) |
CAST({{ ref('orders', 'ordered_at') }} AS DATE) |
TO_DATE() / TO_TIMESTAMP() |
TO_DATE({{ ref('orders', 'ordered_at') }}) |
UPPER() / LOWER() / TRIM() |
UPPER({{ ref('users', 'email') }}) |
| Arithmetic | {{ ref('orders', 'amount') }} + 1 |
| Nested | DATE_TRUNC('month', DATE({{ ref('events', 'ts') }})) |
This is not an exhaustive list — any expression wrapping a single column reference will work. Expressions referencing multiple columns (e.g., {{ ref('a', 'col1') }} + {{ ref('a', 'col2') }}) are not supported and will raise an SST-V049 error.
The expression is passed through to Snowflake as-is in the generated dimension definition. Snowflake validates the SQL at semantic view creation time. To catch SQL typos before generation, use sst validate --snowflake-syntax-check.
How it works:
- SST detects the expression wrapping the column template during parsing
- The base column (e.g.,
ordered_at) is validated against the dbt catalog as usual - At generation time, a synthetic dimension (prefixed
_JK_) is created with the expression - The RELATIONSHIPS clause references the dimension name instead of the raw column
Notes:
- The auto-generated dimension is publicly visible (Snowflake does not support private dimensions)
- If the same expression is used in multiple relationships, the dimension is reused (deduplicated)
- Expressions can appear on either side (or both sides) of a join condition
snowflake_relationships:
- name: customers_to_orders
left_table: {{ table('customers') }}
right_table: {{ table('orders') }}
relationship_conditions:
- "{{ column('customers', 'customer_id') }} = {{ column('orders', 'customer_id') }}"Reusable WHERE clause conditions that are automatically converted to AI_SQL_GENERATION instructions in the generated semantic view DDL.
How it works: Snowflake's
CREATE SEMANTIC VIEWDDL has no nativeFILTERSclause. SST converts your filter definitions into natural language instructions appended to theAI_SQL_GENERATIONclause, guiding Cortex Analyst to apply them by default unless the user explicitly requests unfiltered data. This behavior is controlled by thegeneration.filters_to_instructionsconfig option (default:true).
snowflake_filters:
- name: filter_name
tables:
- {{ table('table_name') }}
description: What this filter does
expr: SQL boolean expression
synonyms:
- alternative_namesnowflake_filters:
- name: active_customers_only
tables:
- {{ table('customers') }}
synonyms:
- active_customers
- exclude_inactive
description: Filters to show only active customers (excludes churned and suspended)
expr: |
{{ column('customers', 'status') }} = 'Active'
AND {{ column('customers', 'account_type') }} != 'Test'Guide Cortex Analyst's behavior with business-specific rules. Custom instructions are automatically included in the generated CREATE SEMANTIC VIEW DDL as AI_SQL_GENERATION and AI_QUESTION_CATEGORIZATION clauses.
When you reference custom instructions in a semantic view using {{ custom_instructions('name') }}, the instruction's ai_sql_generation field is included in the AI_SQL_GENERATION clause and the ai_question_categorization field is included in the AI_QUESTION_CATEGORIZATION clause.
snowflake_custom_instructions:
- name: instruction_name
ai_question_categorization: Instructions for categorizing questions
ai_sql_generation: Instructions for generating SQL queriessnowflake_custom_instructions:
- name: customer_privacy_rules
ai_question_categorization: >
Reject all questions asking about individual customer details. Ask users to contact their admin.
ai_sql_generation: >
When querying customer data, always apply the active_customers_only filter.
Always round currency metrics to 2 decimal places.Validated example queries that train Cortex Analyst and provide onboarding templates. SST emits these as AI_VERIFIED_QUERIES clauses in the generated semantic view DDL.
snowflake_verified_queries:
- name: query_name
tables:
- {{ ref('table_name') }}
question: Natural language question this query answers
use_as_onboarding_question: true # Optional: show in onboarding UI
sql: | # Inline SQL (use this OR sql_file)
SELECT
table_name.column,
COUNT(*) AS total
FROM {{ ref('table_name') }}
GROUP BY 1
verified_by: Data Team
verified_at: "2025-06-15" # ISO date (YYYY-MM-DD) — converted to epoch in DDLFor longer queries, reference an external .sql file instead of inline SQL:
snowflake_verified_queries:
- name: query_name
tables:
- {{ ref('table_name') }}
question: Natural language question this query answers
sql_file: sql/query_name.sql # Path relative to the YAML file
verified_by: Data Team
verified_at: "2025-06-15"Note: Exactly one of
sqlorsql_fileis required — specifying both is a validation error. SQL supports{{ ref('table') }}templates for table name resolution.
snowflake_verified_queries:
- name: monthly_revenue_by_region
tables:
- {{ table('orders') }}
question: What is the total revenue by region for each month?
use_as_onboarding_question: true
sql: |
SELECT
DATE_TRUNC('MONTH', order_date) AS month,
region,
SUM(order_amount) AS total_revenue,
COUNT(DISTINCT order_id) AS order_count
FROM orders
WHERE order_status = 'Completed'
AND order_date >= DATEADD('year', -1, CURRENT_DATE())
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC
verified_by: Data Team
verified_at: "2024-10-04"Combine tables, metrics, and relationships into queryable Snowflake semantic views.
semantic_views:
- name: view_name
description: What this view provides
tables:
- {{ table('table1') }}
- {{ table('table2') }}Optional:
custom_instructions:
- {{ custom_instructions('instruction_name') }}When SST generates semantic views, it automatically includes a WITH EXTENSION (CA='...') clause containing sample_values metadata from your dbt models. This helps Cortex Analyst:
- Understand valid values: Know what categorical values exist (e.g.,
["new", "returning"]for customer_type) - Recognize enums: When
is_enum: true, Cortex knows the sample_values are exhaustive - Generate better queries: More accurate SQL generation with proper value references
The CA extension JSON structure:
{
"tables": [{
"name": "CUSTOMERS",
"dimensions": [
{"name": "CUSTOMER_TYPE", "sample_values": ["new", "returning"], "is_enum": true}
],
"time_dimensions": [
{"name": "CREATED_AT", "sample_values": ["2025-01-15", "2025-02-20"]}
],
"facts": [
{"name": "LIFETIME_SPEND", "sample_values": ["100.50", "250.00"]}
]
}]
}Notes:
- Only columns with non-empty
sample_valuesare included. If no sample_values exist, the CA extension is omitted. - The CA extension is a temporary solution recommended by Snowflake Engineering until native
sample_valuessupport is added to theCREATE SEMANTIC VIEWDDL syntax. Once Snowflake releases official support, SST will be updated to use the native syntax.
semantic_views:
- name: customer_orders_view
description: Combines customer and order data for comprehensive sales analysis
tables:
- {{ table('customers') }}
- {{ table('orders') }}
- name: product_sales_view
description: Product sales data with category and region breakdowns
tables:
- {{ table('products') }}
- {{ table('order_items') }}Validate your models before deployment:
# Validate all models
sst validate --verbose
# Validation checks:
- All templates resolved
- All table references exist in dbt
- All column references exist
- No circular dependencies in metrics
- No duplicate namesTemplate Resolution Error:
ERROR: Template {{ table('user_orders') }} could not be resolved
Available tables: users_orders, customer_orders
Missing Column:
ERROR: Column 'total' not found in table 'orders'
Available columns: amount, quantity, tax, total_amount
Circular Dependency:
ERROR: Circular dependency in metrics:
metric_a → metric_b → metric_c → metric_a
# Bad - unclear abbreviations
- name: m1
expr: SUM(a)
# Good - descriptive and clear
- name: total_subscription_revenue
tables:
- {{ table('subscriptions') }}
description: Total monthly subscription revenue
expr: SUM({{ column('subscriptions', 'monthly_amount') }})- name: customer_lifetime_value
tables:
- {{ table('customers') }}
description: |
Total revenue generated by a customer across all time,
including subscriptions, one-time purchases, and add-ons.
Excludes refunds and chargebacks.
expr: SUM({{ column('customers', 'lifetime_spend') }})metrics/
├── customers.yml # Customer-related metrics
├── sales.yml # Product sales metrics
└── finance.yml # Financial metrics
snowflake_metrics:
# Define base metrics first
- name: gross_revenue
tables:
- {{ table('orders') }}
description: Total revenue before refunds
expr: SUM({{ column('orders', 'amount') }})
- name: refunds
tables:
- {{ table('refunds') }}
description: Total refund amount
expr: SUM({{ column('refunds', 'amount') }})
# Then compose complex metrics
- name: net_revenue
tables:
- {{ table('orders') }}
- {{ table('refunds') }}
description: Revenue after refunds
expr: {{ metric('gross_revenue') }} - {{ metric('refunds') }}- name: average_rating
tables:
- {{ table('reviews') }}
description: Average customer rating (1-5 stars)
expr: AVG({{ column('reviews', 'rating') }})
synonyms:
- avg_rating
- mean_rating
- customer_rating- CLI Reference - Complete command documentation
- CI/CD Guide - CI/CD integration
- Validation Rules - All validation rules