Models
Models define your data sources and their structure.
Sidemantic supports three syntaxes: YAML, SQL, and Python. Examples show all three.
Basic Model
models:
- name: orders
table: orders
primary_key: order_idMODEL (
name orders,
table orders,
primary_key order_id
);from sidemantic import Model
orders = Model(
name="orders",
table="orders",
primary_key="order_id"
)Properties
Required
- name: Unique identifier for the model
- table: Physical table name (or use
sqlfor derived tables) - primary_key: Primary key column (required for joins and symmetric aggregates)
Optional
- sql: SQL query instead of table name (for derived models)
- description: Human-readable description
- relationships: Relationships to other models (see Relationships)
- dimensions: Attributes for grouping and filtering
- metrics: Model-level aggregations
Dimensions
Dimensions are attributes for grouping and filtering:
models:
- name: orders
dimensions:
- name: status
type: categorical
sql: status
- name: order_date
type: time
sql: created_at
granularity: day
- name: is_weekend
type: boolean
sql: "DAYOFWEEK(order_date) IN (6, 7)"
- name: amount
type: numeric
sql: total_amountMODEL (name orders, table orders, primary_key order_id);
DIMENSION (
name status,
type categorical,
sql status
);
DIMENSION (
name order_date,
type time,
sql created_at,
granularity day
);
DIMENSION (
name is_weekend,
type boolean,
sql DAYOFWEEK(order_date) IN (6, 7)
);
DIMENSION (
name amount,
type numeric,
sql total_amount
);from sidemantic import Model, Dimension
orders = Model(
name="orders",
table="orders",
primary_key="order_id",
dimensions=[
Dimension(name="status", type="categorical", sql="status"),
Dimension(name="order_date", type="time", sql="created_at", granularity="day"),
Dimension(name="is_weekend", type="boolean", sql="DAYOFWEEK(order_date) IN (6, 7)"),
Dimension(name="amount", type="numeric", sql="total_amount"),
]
)Dimension Types
- categorical: Text/enum values (status, region, product)
- time: Dates/timestamps with granularity (day, week, month, year)
- boolean: True/false values
- numeric: Numeric dimensions (price tier, quantity bucket)
Time Granularity
- name: order_date
type: time
sql: created_at
granularity: day # hour, day, week, month, quarter, yearQuery with: orders.order_date__month to automatically roll up to month.
Metrics (Model-Level)
Model-level metrics define aggregations on a single model:
models:
- name: orders
metrics:
# Simple aggregations
- name: revenue
agg: sum
sql: amount
- name: order_count
agg: count
- name: avg_order_value
agg: avg
sql: amount
# With filters
- name: completed_revenue
agg: sum
sql: amount
filters: ["status = 'completed'"]
# SQL expressions
- name: total_value
agg: sum
sql: "quantity * price"MODEL (name orders, table orders, primary_key order_id);
-- Simple aggregations
METRIC (
name revenue,
agg sum,
sql amount
);
METRIC (
name order_count,
agg count
);
METRIC (
name avg_order_value,
agg avg,
sql amount
);
-- With filters
METRIC (
name completed_revenue,
agg sum,
sql amount,
filters status = 'completed'
);
-- SQL expressions
METRIC (
name total_value,
agg sum,
sql quantity * price
);from sidemantic import Model, Metric
orders = Model(
name="orders",
table="orders",
primary_key="order_id",
metrics=[
Metric(name="revenue", agg="sum", sql="amount"),
Metric(name="order_count", agg="count"),
Metric(name="avg_order_value", agg="avg", sql="amount"),
Metric(name="completed_revenue", agg="sum", sql="amount",
filters=["status = 'completed'"]),
Metric(name="total_value", agg="sum", sql="quantity * price"),
]
)Aggregation Types
- sum: Sum values
- count: Count rows
- count_distinct: Count unique values
- avg: Average
- min: Minimum value
- max: Maximum value
- median: Median value
See Metrics for graph-level metrics (ratios, derived, cumulative).
Relationships
Define how models join to each other:
models:
- name: orders
table: orders
primary_key: order_id
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
- name: customers
table: customers
primary_key: customer_id
relationships:
- name: orders
type: one_to_many
foreign_key: customer_id-- orders.sql
MODEL (name orders, table orders, primary_key order_id);
RELATIONSHIP (
name customer,
type many_to_one,
foreign_key customer_id
);
-- customers.sql
MODEL (name customers, table customers, primary_key customer_id);
RELATIONSHIP (
name orders,
type one_to_many,
foreign_key customer_id
);from sidemantic import Model, Relationship
orders = Model(
name="orders",
table="orders",
primary_key="order_id",
relationships=[
Relationship(name="customer", type="many_to_one", foreign_key="customer_id")
]
)
customers = Model(
name="customers",
table="customers",
primary_key="customer_id",
relationships=[
Relationship(name="orders", type="one_to_many", foreign_key="customer_id")
]
)See Relationships for details.
Derived Models
Use SQL instead of a table:
models:
- name: weekly_summary
sql: |
SELECT
DATE_TRUNC('week', order_date) as week,
SUM(amount) as total
FROM orders
GROUP BY 1
primary_key: weekComplete Example
models:
- name: orders
table: orders
primary_key: order_id
description: "Customer orders"
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
dimensions:
- name: status
type: categorical
sql: status
- name: order_date
type: time
sql: created_at
granularity: day
- name: customer_tier
type: categorical
sql: |
CASE
WHEN total_amount > 1000 THEN 'premium'
WHEN total_amount > 100 THEN 'standard'
ELSE 'basic'
END
metrics:
- name: revenue
agg: sum
sql: amount
description: "Total order revenue"
- name: order_count
agg: count
description: "Number of orders"
- name: avg_order_value
agg: avg
sql: amount
- name: completed_revenue
agg: sum
sql: amount
filters: ["status = 'completed'"]
- name: large_orders
agg: count
filters: ["amount > 1000"]See examples/sql/orders.sql for the complete SQL version:
MODEL (
name orders,
table orders,
primary_key order_id,
description 'Customer orders'
);
-- Relationships
RELATIONSHIP (
name customer,
type many_to_one,
foreign_key customer_id
);
-- Dimensions
DIMENSION (
name status,
type categorical,
sql status
);
DIMENSION (
name order_date,
type time,
sql created_at,
granularity day
);
DIMENSION (
name customer_tier,
type categorical,
sql CASE WHEN total_amount > 1000 THEN 'premium' WHEN total_amount > 100 THEN 'standard' ELSE 'basic' END
);
-- Metrics
METRIC (
name revenue,
agg sum,
sql amount,
description 'Total order revenue'
);
METRIC (
name order_count,
agg count,
description 'Number of orders'
);
METRIC (
name avg_order_value,
agg avg,
sql amount
);
METRIC (
name completed_revenue,
agg sum,
sql amount,
filters status = 'completed'
);
METRIC (
name large_orders,
agg count,
filters amount > 1000
);from sidemantic import Model, Dimension, Relationship, Metric
orders = Model(
name="orders",
table="orders",
primary_key="order_id",
description="Customer orders",
relationships=[
Relationship(name="customer", type="many_to_one", foreign_key="customer_id")
],
dimensions=[
Dimension(name="status", type="categorical", sql="status"),
Dimension(name="order_date", type="time", sql="created_at", granularity="day"),
Dimension(
name="customer_tier",
type="categorical",
sql="""CASE
WHEN total_amount > 1000 THEN 'premium'
WHEN total_amount > 100 THEN 'standard'
ELSE 'basic'
END"""
),
],
metrics=[
Metric(name="revenue", agg="sum", sql="amount", description="Total order revenue"),
Metric(name="order_count", agg="count", description="Number of orders"),
Metric(name="avg_order_value", agg="avg", sql="amount"),
Metric(name="completed_revenue", agg="sum", sql="amount", filters=["status = 'completed'"]),
Metric(name="large_orders", agg="count", filters=["amount > 1000"]),
]
)Best Practices
- Models: plural nouns (
orders,customers) - Dimensions: descriptive names (
status,order_date,customer_region) - Metrics: what they measure (
revenue,order_count,avg_order_value)
Always set primary_key - it’s required for: - Joins between models - Symmetric aggregates (fan-out handling) - Proper deduplication
Use SQL expressions in sql fields: - sql: "UPPER(status)" - transform dimension - sql: "quantity * price" - calculated metric - Supports any SQL your database supports