← Docs
Write your first model
Model two warehouse tables — customer and order — into entities, measures, a relationship, and the metrics your consumers are allowed to ask for.
Updated
A semantic model is a folder of YAML. This walks through the smallest useful
one: a customer table and an order table, turned into governed metrics like
“average order value by region.”
The tables
customer order
id (primary key) id (primary key)
region text customer_id → customer.id
signup_date date status text
ltv numeric order_date timestamp
amount numeric
The rules
- Entity — a business noun, usually one table, with a primary key.
- Dimension — an attribute you slice by. Categorical, never aggregated.
- Measure — one expression plus one aggregation.
- Metric — a named formula over measures, computed after aggregation.
- Relationship — a named edge in the join graph, many-side to one-side.
- Filter — always
{ dimension, op, value }, never raw SQL.
The model
entities:
- name: Customer
table: public.customer
primary_key: id
dimensions:
- name: region
column: region
type: string
values: [na, emea, apac]
- name: signup_date
column: signup_date
type: date
grains: [month, quarter, year]
measures:
- name: customer_count
expr: id
agg: count
- name: lifetime_value
expr: ltv
agg: sum
- name: Order
table: public.order
primary_key: id
dimensions:
- name: status
column: status
type: string
values: [pending, completed, cancelled, refunded]
- name: order_date
column: order_date
type: timestamp
grains: [day, week, month, quarter, year]
measures:
- name: revenue
expr: amount
agg: sum
- name: order_count
expr: id
agg: count
- name: unique_customers
expr: customer_id
agg: count_distinct
relationships:
- name: order_customer
from: Order
to: Customer
cardinality: many_to_one
keys:
- [customer_id, id]
metrics:
- name: avg_order_value
expr: revenue / order_count
description: Sum of order amounts divided by order count.
- name: orders_per_customer
expr: order_count / customer_count
Things the compiler cares about
- Primary keys are mandatory. The compiler needs them to keep a measure correct when a query joins across a one-to-many relationship (fan-out).
- No
avgaggregation. “Average order value” is a metric —revenue / order_count, computed after aggregation — notavg(amount)on raw rows. count_distinctis non-additive. It does not freely re-aggregate across time or dimensions the waysumandcountdo.- Relationship names are unique model-wide. If two entities connect more
than one way (
bill_to,ship_to), each edge needs its own name so a query can pick one.
Apply, then publish
Two separate actions, on purpose:
- Apply draft writes your changes into the working model and validates it. Errors block here, not in production.
- Publish freezes the current draft into an immutable artifact — the model plus its policy bundle, versioned. Consumers only ever see published artifacts.
Next: Query the API.