Relationships
Define relationships between models for automatic joining.
Relationship Types
many_to_one
Many records in THIS model → one record in OTHER model:
models:
- name: orders
table: orders
primary_key: order_id
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id # Column in orders tableSQL: LEFT JOIN customers ON orders.customer_id = customers.customer_id
Meaning: Many orders belong to one customer.
one_to_many
One record in THIS model → many records in OTHER model:
models:
- name: customers
table: customers
primary_key: customer_id
relationships:
- name: orders
type: one_to_many
foreign_key: customer_id # Column in orders table (the OTHER table)SQL: LEFT JOIN orders ON customers.customer_id = orders.customer_id
Meaning: One customer has many orders.
one_to_one
One record in THIS model → one record in OTHER model:
models:
- name: orders
table: orders
primary_key: order_id
relationships:
- name: invoice
type: one_to_one
foreign_key: order_id # Column in invoice tableSQL: LEFT JOIN invoice ON orders.order_id = invoice.order_id
Meaning: One order has one invoice.
Bidirectional Relationships
Define from both sides for flexibility:
models:
- name: orders
primary_key: order_id
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
- name: customers
primary_key: customer_id
relationships:
- name: orders
type: one_to_many
foreign_key: customer_idNow you can query from either direction: - SELECT orders.revenue, customers.name FROM orders - SELECT customers.name, orders.revenue FROM customers
Multi-Hop Joins
Define relationships and query across 2+ models automatically:
models:
- name: orders
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
- name: customers
relationships:
- name: region
type: many_to_one
foreign_key: region_id
- name: regions
table: regions
primary_key: region_idQuery spans 2 hops automatically:
# orders -> customers -> regions
layer.sql("""
SELECT orders.revenue, regions.country
FROM orders
""")Relationship Properties
- name: Name of the related model
- type:
many_to_one,one_to_many, orone_to_one - foreign_key: The foreign key column name
- primary_key: (Optional) Primary key in related table (defaults to related model’s primary_key)
Python API
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"),
Relationship(name="order_items", type="one_to_many", foreign_key="order_id")
]
)
customers = Model(
name="customers",
table="customers",
primary_key="customer_id",
relationships=[
Relationship(name="orders", type="one_to_many", foreign_key="customer_id")
]
)Symmetric Aggregates
When you have fan-out (one-to-many joins), Sidemantic automatically uses symmetric aggregates to prevent double-counting:
models:
- name: orders
metrics:
- name: revenue
agg: sum
sql: amount
relationships:
- name: order_items
type: one_to_many
foreign_key: order_id# Query with fan-out
layer.sql("""
SELECT orders.revenue, order_items.quantity
FROM orders
""")Automatically generates:
SUM(DISTINCT HASH(orders.order_id)::HUGEINT * (1::HUGEINT << 20) + orders.revenue)This ensures revenue is counted once per order, not once per item.
Best Practices
- many_to_one: FK is in THIS table (e.g., orders.customer_id)
- one_to_many: FK is in the OTHER table (e.g., customers ← orders.customer_id)
- one_to_one: FK is in the OTHER table, expects one record
Set primary_key on all models involved in relationships:
models:
- name: orders
table: orders
primary_key: order_id # Required!For maximum flexibility, define relationships from both models:
# orders -> customers (many-to-one)
- name: orders
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
# customers -> orders (one-to-many)
- name: customers
relationships:
- name: orders
type: one_to_many
foreign_key: customer_idCommon Patterns
One-to-Many
# Order has many items
- name: orders
primary_key: order_id
relationships:
- name: order_items
type: one_to_many
foreign_key: order_id
# Item belongs to order (inverse: many-to-one)
- name: order_items
primary_key: item_id
relationships:
- name: order
type: many_to_one
foreign_key: order_idMany-to-One
# Order belongs to customer
- name: orders
primary_key: order_id
relationships:
- name: customer
type: many_to_one
foreign_key: customer_id
# Customer has many orders (inverse: one-to-many)
- name: customers
primary_key: customer_id
relationships:
- name: orders
type: one_to_many
foreign_key: customer_idOne-to-One
# Order has one invoice
- name: orders
primary_key: order_id
relationships:
- name: invoice
type: one_to_one
foreign_key: order_id
# Invoice belongs to order (inverse: many-to-one, but unique)
- name: invoice
primary_key: invoice_id
relationships:
- name: order
type: many_to_one
foreign_key: order_idTroubleshooting
Ensure you’ve defined relationships between the models:
models:
- name: orders
relationships:
- name: customer
type: many_to_one
foreign_key: customer_idIf joins aren’t working, check the foreign_key: - many_to_one: Column in THIS table - one_to_many/one_to_one: Column in OTHER table