Skip to main content

Cardinality

Understand relationship cardinality types.

Many-to-Many Not Supported

Strata does not support many_to_many relationships. Use junction/bridge tables with two separate relationships (one_to_many and many_to_one) instead.

Overview

Cardinality defines the relationship type between two tables. It tells the planner how rows in one table relate to rows in another.

Cardinality Types

many_to_one

Most common type. Many rows in left table match one row in right table.

Example: Many orders belong to one customer

orders_customer:
left: Orders
right: Customer
sql: left.customer_id = right.id
cardinality: many_to_one

Use cases:

  • Fact table to dimension table
  • Child to parent relationships
  • Foreign key relationships

one_to_many

One row in left table matches many rows in right table.

Example: One customer has many orders

customer_orders:
left: Customer
right: Orders
sql: left.id = right.customer_id
cardinality: one_to_many

Use cases:

  • Parent to child relationships
  • One-to-many associations
  • Reverse of many_to_one

one_to_one

One row in left table matches one row in right table.

Example: One user has one profile

user_profile:
left: User
right: User Profile
sql: left.id = right.user_id
cardinality: one_to_one

Use cases:

  • User profiles
  • Settings tables
  • Extension tables

many_to_many Not Supported

⚠️ Strata does not support many_to_many relationships.

Workaround: Create a junction/bridge table and use two relationships:

# Instead of many_to_many:
# Users <-> Roles

# Create:
# Users -> UserRoles -> Roles

users_user_roles:
left: Users
right: User Roles
sql: left.id = right.user_id
cardinality: one_to_many

user_roles_roles:
left: User Roles
right: Roles
sql: left.role_id = right.id
cardinality: many_to_one

Choosing Cardinality

Fact to Dimension (many_to_one)

sales_customer:
left: Sales # Fact table (many rows)
right: Customer # Dimension table (one row per customer)
sql: left.customer_id = right.id
cardinality: many_to_one

Dimension to Dimension (one_to_one)

customer_address:
left: Customer # One customer
right: Address # One address per customer
sql: left.address_id = right.id
cardinality: one_to_one

Parent to Child (one_to_many)

customer_orders:
left: Customer # One customer
right: Orders # Many orders per customer
sql: left.id = right.customer_id
cardinality: one_to_many

Measure Expansion

Understanding allow_measure_expansion

By default, measures can only be aggregated along one_to_one join paths. The allow_measure_expansion property enables measures to aggregate along many_to_one or one_to_many paths, but use with caution to avoid double-counting.

The Double-Counting Problem

When you join a fact table to a dimension through a many_to_one relationship and then to another table, measures might be counted multiple times:

Orders (fact) → Customer → Customer Address
100 rows 50 rows 50 rows

If you query Total Revenue grouped by Address City, each order amount could be counted once (correct) or multiple times (incorrect) depending on the join path.

Using allow_measure_expansion

Set allow_measure_expansion: true when you're certain the join path won't cause double-counting:

# Safe: Customer has exactly one address
customer_address:
left: Customer
right: Customer Address
sql: left.address_id = right.id
cardinality: many_to_one
allow_measure_expansion: true # Measures can flow through this join

When to Use

Scenarioallow_measure_expansionWhy
Fact → Dimension (standard)Not neededDefault behavior handles this
Dimension → Dimension (1:1 logical)trueSafe when truly 1:1 in practice
Dimension → Lookup tabletrueSafe for simple lookups
Any path that could fan outfalse (default)Prevents double-counting

Example: Safe Expansion

datasource: warehouse

# Orders to Customer: standard fact-to-dimension
orders_customer:
left: Orders
right: Customer
sql: left.customer_id = right.id
cardinality: many_to_one

# Customer to Address: each customer has one address
# Safe to expand measures through this join
customer_address:
left: Customer
right: Customer Address
sql: left.address_id = right.ca_address_sk
cardinality: many_to_one
allow_measure_expansion: true

With this configuration, you can query Total Revenue by Address City and get correct results.

Automatic Behavior

  • one_to_one relationships: Measures always expand (bidirectional)
  • many_to_one without flag: Only dimensions expand, measures stop
  • many_to_one with allow_measure_expansion: true: Measures expand through

Internal Behavior Notes

How Strata Handles Cardinality
  • one_to_many is automatically converted to many_to_one internally (direction flipped)
  • one_to_one creates bidirectional joins automatically — you can traverse either direction
  • Tables in a relationship must be in the same datasource

Best Practices

  1. Use many_to_one for fact-to-dimension relationships
  2. Match database relationships - Cardinality should reflect actual data
  3. Avoid many_to_many - Use junction tables instead
  4. Use allow_measure_expansion sparingly - Only when you're certain it won't cause double-counting
  5. Document relationships - Add comments explaining why expansion is safe

Next Steps