Semantic model
Entities, dimensions, measures, metrics, grains, and join graphs in a datahashi model.
Updated
This page defines the terms in a datahashi model. The same terms appear in the YAML spec, the REST API, the MCP tools, and the console.
flowchart LR
subgraph O["Entity: Order"]
direction TB
O1["Dimensions: id (PK), order_date (day), status<br/>Measures: revenue, order_count"]
end
subgraph Cu["Entity: Customer"]
direction TB
C1["Dimensions: id (PK), region, plan_tier<br/>Measures: customer_count"]
end
O -->|"many-to-one join<br/>(Join Graph)"| Cu
O --> M["Top-level metrics<br/>avg_order_value = revenue / order_count<br/>revenue_growth_yoy = (revenue − offset(revenue, 1 year)) / offset(revenue, 1 year)"]
Modeling primitives
Semantic Model
The versioned set of definitions that maps warehouse tables to business names. The YAML file has no name key. A published model has a version. Pin that version with version on a query. The model header source names the Connection.
Entity
A business noun with a primary key and relationships to other entities. Examples: Customer, Order.
- Physical mapping. A warehouse table (
table: public.orders) or custom SQL (sql: "SELECT ... FROM ..."). - Inheritance. An entity can extend another entity with
extends: BaseEntity. It inherits fields. - Join sugar. An entity can declare
joins:to related entities.
Dimension
An attribute of an entity. You group, filter, or slice on it. Examples: region, status, ordered_at.
- Standard dimension. A warehouse column.
- Calculated dimension. An opaque SQL expression (
expr: "CASE WHEN amount > 100 THEN 'large' ELSE 'small' END"). - Time dimension. Declares time buckets called grains (for example
[day, month, quarter, year]). - Fiscal calendar. Set
fiscal_year_start_month(1–12) to usefiscal_quarterandfiscal_year.
Grain
A time bucket on a time dimension in a query.
- Standard grains:
minute,hour,day,week(ISO-Monday),month,quarter,year. - Fiscal grains:
fiscal_quarter,fiscal_year. These needfiscal_year_start_monthon the dimension. - Each selected time dimension takes at most one grain per query.
Measure vs Metric
The compiler treats Measure and Metric as different kinds:
| Concept | Definition | Example | Who may select it? |
|---|---|---|---|
| Measure | Raw aggregatable field on an entity. Evaluated in the first aggregation. | sum(amount), count(id), min(price) |
Only a grant that exposes that measure |
| Metric | Named formula over measures. Computed after aggregation. | revenue / order_count |
A grant on the metric, when every entity it reads is also granted |
Measure aggregations
- Additive:
sum,count,min,max. - Non-additive / approx:
count_distinct,count_distinct_approx. - Percentiles:
median,p90,p95,p99(PostgreSQL, Snowflake, BigQuery, DuckDB). - Filtered measures: Conditions inside the aggregate (
filters: [{ dimension: status, op: eq, value: completed }]). - Rolling windows: Trailing or cumulative time windows (
rolling_window: { trailing: 30 day }).
Metric expressions
- Arithmetic. A formula over measures and constants (
expr: revenue / order_count). - Derived metrics. A metric can name another metric (
expr: profit / revenue). The engine inlines the reference at publish. It rejects cycles. - Period-over-period.
offset(expr, interval)(for exampleoffset(revenue, 1 year)). After inlining,exprmust be one bare measure. - Multi-stage metrics. Two-pass aggregations such as
avg_per(revenue, customer_id)ormax_per(revenue, store_id). The engine computes a value per entity first. Then it computes the outer metric.
Join Graph
The directed graph of entities. Named relationships carry cardinality (many_to_one, one_to_many, one_to_one). There is no many_to_many. Model a bridge entity.
The compiler resolves join paths. Relationship names are unique in the model. Role-playing joins such as bill_to_customer and ship_to_customer stay distinct.
Segment
A named filter on the model (segments:). A query can reference it by name. Segments expand into concrete filters before row-level security runs.
View
A curated projection of a semantic model. It exposes a subset of metrics and dimensions. It can add predefined filters. A view can only narrow access. It cannot widen access. It cannot bypass data roles.
Query and access
Semantic Query
The structured request over REST or MCP. It contains:
version: Optional published model version. There is nomodelfield.metrics: Governed metric names.measures: Raw measure names. Refused when the caller’s grants omit them.dimensions: Group-by dimensions with optionalgrain.filters: Flat{ dimension, op, value }conditions, combined with AND.filter_group: Nested{op, filters, groups}boolean logic.having:{ ref, op, value }conditions after aggregation.order:{ ref, desc }sort specifications.limitandoffset: Pagination.offsetneeds a positivelimitand anorder.
Governed mode vs Explore mode
- Grants without raw measures. The usual surface for API consumers, agents, and apps. Named metrics and curated views only. A raw measure is refused.
- Grants that include measures. Metrics and raw measures across the Join Graph. This is not a separate request mode.
Data Role
A named set of grants in the policy bundle. A role grants entities, fields, and metrics. It can attach a row filter (row_filter: "region = current_user.region"). A principal with several data roles gets a union of grants and an intersection of row filters.
Resilience and metadata
Drift resilience
- Rename alias (
renamed_from). Previous names for a dimension, measure, or metric. The compiler maps the old name to the new name. - Deprecation (
deprecated: true). The field stays queryable. Catalog discovery shows a warning.
Documentation metadata
tags:. String labels shown on catalog entries. They are not a filter onPOST /v1/catalog.owner:. Team or person who maintains the definition.meta:. Key-value bag for downstream tools.
Next: Query Runner · Rollups · Data roles and row-level security.