Skip to main content

Slicing

Slice syntax mirrors Python: [start:end] or [start:end:step].

Forms

FormMeaning
[a:b]Rows a (inclusive) through b (exclusive)
[:b]First b rows
[a:]From row a to the end
[:]Every row (rare; same as no slice)
[a:b:s]Same as [a:b] but step s

Negative indices

Negative numbers count from the end:

shop.customers[-10:]    // last 10
shop.customers[:-1] // all but the last

Filters, slices, and aggregates combine in any order

You can chain as many as you want, and the optimizer figures out the best execution plan. These are all equivalent:

shop.orders[status = "paid"]._desc(placed_at)[0:20]
shop.orders._desc(placed_at)[status = "paid"][0:20]
shop.orders[total > 100][status = "paid"]._desc(placed_at)[0:20]

Multiple filters act like and:

shop.orders[status = "paid"][total > 100]
// same as
shop.orders[status = "paid" and total > 100]

Use whichever form reads better. For supported patterns this becomes a single indexed range read in the storage layer.

Slice on a relation

You can slice the related side of a projection too:

shop.customers{
id,
orders[0:5]{id, total}
}

Response:

[
{
"id": "c1",
"orders": [
{ "id": "o1", "total": 99.00 },
{ "id": "o2", "total": 45.50 }
]
}
]

Each customer gets their first 5 orders.

Out-of-range

Slices that overshoot the data simply return what's there:

shop.customers[1000:2000]    // empty array on a 50-row table

This never throws.