Skip to content

Commit 98ac2c5

Browse files
committed
DEV: FT.AGG expression precedence
1 parent 4165309 commit 98ac2c5

File tree

2 files changed

+312
-43
lines changed

2 files changed

+312
-43
lines changed

content/develop/interact/search-and-query/advanced-concepts/aggregation-syntax.md

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,101 @@ weight: 2
1717

1818
## Overview
1919

20-
[`FT.AGGREGATE`]({{< relref "/commands/ft.aggregate" >}}) is a powerful Redis Query Engine (RQE) command for performing advanced data aggregation, filtering, sorting, and transformations on indexed documents. This reference page provides a structured breakdown of syntax, ordering rules, and best practices.
20+
[`FT.AGGREGATE`]({{< relref "/commands/ft.aggregate" >}}) is a powerful Redis Query Engine (RQE) command for performing advanced data aggregation, filtering, sorting, and transformations on indexed hash or JSON documents. This reference page provides a structured breakdown of syntax, ordering rules, and best practices.
2121

22-
The [main aggregations page]({{< relref "/develop/interact/search-and-query/advanced-concepts/aggregations" >}}) has a simple diagram showing how `FT.AGGREGATE` pipelines are constructed, but it's a bit too simplistic. For example, it's possible to create more complex aggregation pipelines by applying multiple `REDUCE` functions under a single GROUPBY clause, or you can chain groupings and mix in additional mapping steps: `GROUPBY` ... `REDUCE` ... `APPLY` ... `GROUPBY` ... `REDUCE`.
22+
The [main aggregations page]({{< relref "/develop/interact/search-and-query/advanced-concepts/aggregations" >}}) has a simple diagram showing how `FT.AGGREGATE` pipelines are constructed, but it doesn't tell the whole story. For example, it's possible to create more complex aggregation pipelines by applying multiple `REDUCE` functions under a single `GROUPBY` clause, or you can chain groupings and mix in additional mapping steps:
23+
24+
`GROUPBY` ... `REDUCE` ... `APPLY` ... `GROUPBY` ... `REDUCE`
25+
26+
{{< note >}}
27+
The examples on this page are based on a hypothetical "products" data set, which you can [download here](./data/products.txt).
28+
{{< /note >}}
2329

2430
## Syntax and expression ordering
2531

2632
The `FT.AGGREGATE` command processes multiple expressions in a pipeline. Below is the recommended order:
2733

34+
1. `index` – the name of your index, which must be the first argument.
35+
1. `query` – your query, which must be the second argument.
2836
1. `FILTER` – filters raw documents before transformations or aggregation.
2937
1. `LOAD` – loads additional document attributes.
3038
1. `APPLY` – applies transformations on fields.
3139
1. `GROUPBY` – groups results by specific fields.
3240
1. `REDUCE` – performs aggregations. For example, `SUM`, `COUNT`, and `AVG`.
3341
1. `SORTBY` – orders the results based on specified fields.
3442
1. `LIMIT` – restricts the number of results returned.
43+
1. `DIALECT 2` - provides for more comprehensive syntax, for example using parameters in `FILTER` expressions.
44+
45+
Other keywords will be discussed toward the end of this page.
46+
47+
## When to use `@`
3548

36-
## Using GROUPBY with multiple REDUCE
49+
Fields must be preceded by `@` in the following circumstances:
50+
51+
- When referencing fields loaded from documents. In the following example, `price` is a document field and must be prefixed wit `@`.
52+
53+
```sh
54+
FT.AGGREGATE products "*"
55+
LOAD 1 @price
56+
APPLY "@price * 1.1" AS adjusted_price
57+
SORTBY 2 @adjusted_price DESC
58+
LIMIT 0 10
59+
```
60+
61+
- When referencing fields inside a `FILTER` clause that were loaded from documents.
62+
63+
```sh
64+
FT.AGGREGATE products "*"
65+
LOAD 1 @rating
66+
FILTER "@rating >= 4.5"
67+
LIMIT 0 10
68+
```
69+
70+
- When referencing fields inside `GROUPBY` or `REDUCE` clauses.
71+
72+
```sh
73+
FT.AGGREGATE products "*"
74+
GROUPBY 1 @category
75+
REDUCE SUM 1 @price AS total_price
76+
LIMIT 0 10
77+
```
78+
79+
- When referencing fields created by `REDUCE` in an `APPLY` or `FILTER` clauses.
80+
81+
```sh
82+
FT.AGGREGATE products "*"
83+
GROUPBY 1 @category
84+
REDUCE SUM 1 @price AS total_price
85+
APPLY "@total_price * 1.2" AS boosted_price
86+
FILTER "@total_price > 1000"
87+
LIMIT 0 10
88+
```
89+
90+
- When referencing fields created by `APPLY` in another `APPLY` or `FILTER` clause.
91+
92+
```sh
93+
FT.AGGREGATE products "*"
94+
LOAD 2 @price @discount
95+
APPLY "@price - @discount" AS net_price
96+
APPLY "@net_price * 1.1" AS marked_up
97+
FILTER "@net_price > 200"
98+
LIMIT 0 10
99+
```
100+
101+
- When referencing fields created by `APPLY` in a `SORTBY` clause.
102+
103+
```sh
104+
FT.AGGREGATE products "*"
105+
LOAD 2 @price @discount
106+
APPLY "@price - @discount" AS net_price
107+
SORTBY 2 @net_price DESC
108+
LIMIT 0 10
109+
```
110+
111+
## GROUPBY with multiple REDUCE operations
37112

38113
`GROUPBY` can be followed by multiple `REDUCE` operations for different aggregations.
114+
39115
```sh
40116
FT.AGGREGATE products "*"
41117
GROUPBY 1 @category
@@ -46,30 +122,34 @@ FT.AGGREGATE products "*"
46122
LIMIT 0 10
47123
```
48124

49-
## Using multiple APPLY, GROUPBY, and REDUCE
125+
## Multiple APPLY operations followed by GROUPBY and REDUCE
50126

51127
`APPLY` can be used in various ways before and after `GROUPBY` and `REDUCE`.
128+
52129
```sh
53130
FT.AGGREGATE products "*"
54-
APPLY "@price - @discount AS final_price"
55-
APPLY "@final_price * @quantity AS total_revenue"
131+
LOAD 3 @price @discount @quantity
132+
APPLY "@price - @discount" AS final_price
133+
APPLY "@final_price * @quantity" AS total_revenue
56134
GROUPBY 1 @category
57-
REDUCE SUM 1 total_revenue AS total_category_revenue
58-
SORTBY 2 total_category_revenue DESC
135+
REDUCE SUM 1 @total_revenue AS total_category_revenue
136+
SORTBY 2 @total_category_revenue DESC
59137
LIMIT 0 10
60138
```
61139

62-
## Using FILTER and PARAMS
140+
## FILTER and PARAMS
63141

64142
`FILTER` is used to remove unwanted records, while `PARAMS` allows parameterized queries.
143+
65144
```sh
66145
FT.AGGREGATE products "*"
67-
PARAMS 2 "min_price" 500 "min_rating" 4.0
68-
FILTER "@price >= $min_price"
69-
FILTER "@rating >= $min_rating"
70-
APPLY "@price * @quantity AS total_value"
71-
SORTBY 2 total_value DESC
146+
LOAD 3 @price @rating @quantity
147+
FILTER "@price >= 500"
148+
FILTER "@rating >= 4.0"
149+
APPLY "@price * @quantity" AS total_value
150+
SORTBY 2 @total_value DESC
72151
LIMIT 0 10
152+
DIALECT 2
73153
```
74154

75155
## Placement of FILTER before and after GROUPBY/APPLY
@@ -79,47 +159,35 @@ FT.AGGREGATE products "*"
79159
- **Before APPLY:** Ensures calculations are applied only to certain records.
80160
- **After APPLY:** Filters computed values.
81161

82-
## Using LOAD after GROUPBY/REDUCE
162+
## LOAD after GROUPBY/REDUCE
83163

84-
`LOAD` is generally used **before** `GROUPBY`, but in some cases, it can be used afterward to retrieve document metadata.
85-
```sh
86-
FT.AGGREGATE products "*"
87-
GROUPBY 1 @category
88-
REDUCE COUNT 0 AS product_count
89-
REDUCE SUM 1 @price AS total_price
90-
WITHCURSOR COUNT 5
91-
```
164+
This is not allowed and you'll get a syntax error.
92165

93166
## Placement rules for specific parameters
94167

95-
| Parameter | Placement |
96-
|-----------|----------------|
97-
| `TIMEOUT` | Can be placed anywhere |
98-
| `LIMIT` | Must be at the end |
99-
| `WITHCURSOR` | Must be at the end |
100-
| `SCORER` | Can be placed anywhere |
101-
| `ADDSCORES` | Must be before sorting |
102-
| `DIALECT` | Must be at the end |
168+
| Parameter | Placement |
169+
|----- |----- |
170+
| `TIMEOUT` | Can be placed anywhere. |
171+
| `LIMIT` | Must be at the end, before `DIALECT`. |
172+
| `WITHCURSOR` | Must be at the end, before `DIALECT`. |
173+
| `SCORER` | Can be placed anywhere. |
174+
| `ADDSCORES` | Must be before sorting. |
175+
| `DIALECT` | Must be at the end. |
103176

104-
## LIMIT and WITHCURSOR are mutually exclusive
177+
## LIMIT and WITHCURSOR used together
178+
179+
In practical terms, `LIMIT` and `WITHCURSOR` are mutually exclusive. However, they can be used together.
180+
`LIMIT` returns immediate results, while `WITHCURSOR` retrieves results incrementally using the [cursor API]({{< relref "/develop/interact/search-and-query/advanced-concepts/aggregations/#cursor-api" >}}).
105181

106-
`LIMIT` returns immediate results, while `WITHCURSOR` retrieves results incrementally.
107182
```sh
108183
FT.AGGREGATE products "*"
109184
GROUPBY 1 @category
110185
REDUCE COUNT 0 AS product_count
111-
WITHCURSOR COUNT 5
186+
LIMIT 0 100
187+
WITHCURSOR COUNT 3
112188
```
113189

114-
## Summary
115-
116-
- `GROUPBY` allows multiple `REDUCE` functions.
117-
- `APPLY` can be positioned before or after grouping.
118-
- `FILTER` placement affects results significantly.
119-
- `LOAD` after `GROUPBY` is only useful in specific cases.
120-
- `LIMIT` and `WITHCURSOR` **cannot** be used together.
190+
See the following resources for more information:
121191

122-
For further reference:
123192
- [`FT.AGGREGATE` command page](https://redis.io/docs/latest/commands/ft.aggregate/)
124193
- [RQE source code](https://github.com/RediSearch/RediSearch/tree/master/src/aggregate)
125-

0 commit comments

Comments
 (0)