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 :)