Skip to main content

Cross-model queries

In SQL, every query that touches more than one table starts with a JOIN. AQL flips that around: relationships are declared once in your dataset, then any query just references the columns it needs.

Try it interactively

Practice in the AQL Playground: Relationships.

The basic move​

You're querying order_items. You want each item's product name (which lives on products). Just ask:

order_items
| select(order_items.id, products.name)

No join. AQL sees that order_items is related to products, generates the right join under the hood, and gives you the result.

Why this works​

A dataset isn't just a list of tables. It's tables plus the relationships between them. Each model represents a business entity; each relationship records how those entities connect:

  • An order_items row belongs to one orders row.
  • An orders row belongs to one customers row.
  • A products row belongs to one merchants row.

Because the relationships are known up front, AQL can resolve products.name from inside an order_items query the same way you'd say it in plain English.

Multi-hop is fine too​

You can reach further as long as each hop is many-to-one:

order_items
| select(order_items.id, merchants.name)

Here AQL walks order_items → products → merchants. One item belongs to one product, which belongs to one merchant. So the chain is unambiguous.

The one rule: many-to-one only​

You can reference the "one" side of a relationship freely. The "many" side is different. There isn't a single row to point at. From an orders row, order_items is a collection, not a value:

// This doesn't work: order_items.value is many rows
orders | select(orders.id, orders.discount * order_items.value)

To use the many side, you have to aggregate it first:

orders | select(orders.id, orders.discount * sum(order_items.value))

sum() collapses the collection back to a single number.

When relationships are ambiguous​

If two models are connected by more than one path, AQL won't guess. Either deactivate the paths you don't want in the dataset, or override per-metric with with_relationships():

sum(order_items.revenue)
| with_relationships(order_items.product_id > products.id)

Next​

→ Defining a metric: name an aggregation so you can reuse it instead of writing it out every time.


Open Markdown
Let us know what you think about this document :)