The pipe operator
The pipe operator | takes whatever's on the left and passes it as the first argument to whatever's on the right. It's the syntax that makes AQL feel composable instead of nested.
// These two are equivalent:
users | avg(users.age)
avg(users, users.age)
Why it matters
AQL is composable by design: every function takes input, produces output, and that output can feed straight into the next function. You don't strictly need pipe for that. sum(filter(orders, orders.country = 'Singapore'), orders.total_value) works fine. But it reads inside-out, and three or four steps deep it stops being readable.
Pipe is what makes that composability ergonomic. SQL forces one shape (SELECT ... FROM ... WHERE ... GROUP BY ...); AQL lets you break a query into small steps and chain them, so the code reads in the same order you think about it: start with a table, narrow it down, aggregate. The pipe is the signal that AQL is built to be read left-to-right, step by step.
Example
Say you want total order value for Singapore. You need two steps:
With pipes:
orders
| filter(orders.country = 'Singapore')
| sum(orders.total_value)
Read it top to bottom: take orders, keep only Singapore, sum the totals. Each pipe feeds its left side into the next function's first argument.
Each step that takes a table walks it one row at a time. filter(orders.country = 'Singapore') checks the condition on each row; sum(orders.total_value) adds the value column row by row. Whenever you write an expression like orders.country or orders.quantity * orders.price, it's evaluated against whichever row the step is currently looking at (the current row). This per-row evaluation is the foundation that filter, group, and aggregate all build on.
Tables in, scalars out
Every AQL expression produces either a table (rows you can keep filtering or grouping) or a scalar (a single value, the end of a chain). The pipe respects that:
orders→ a table. You can keep piping.orders | filter(...)→ still a table. Keep piping.orders | sum(orders.total_value)→ a scalar. The chain ends. There's nothing left to filter.
If a step expects a table and you hand it a scalar (or vice versa), the chain breaks. Once you've internalized "what shape is this step's output?", reading AQL gets a lot easier.
When pipes shine
- Long chains (3+ steps): readable top-to-bottom instead of nested.
- Reusable fragments:
users | filter(users.is_active)can be a building block. - Debugging: comment out a single line to see the intermediate result.
For the full signature and edge cases, see the pipe reference.
Next
→ Filtering: where() vs filter(), and when each one applies.