|
| 1 | +--- |
| 2 | +categories: |
| 3 | +- docs |
| 4 | +- develop |
| 5 | +- stack |
| 6 | +- oss |
| 7 | +- rs |
| 8 | +- rc |
| 9 | +- oss |
| 10 | +- kubernetes |
| 11 | +- clients |
| 12 | +description: Order of operations for the FT.AGGREGATE command |
| 13 | +linkTitle: Aggregation syntax |
| 14 | +title: FT.AGGREGATE order of operations |
| 15 | +weight: 2 |
| 16 | +--- |
| 17 | + |
| 18 | +## Overview |
| 19 | + |
| 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. |
| 21 | + |
| 22 | +The [main aggregations page]({{< relref "/develop/ai/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, you can 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 >}} |
| 29 | + |
| 30 | +## Syntax and expression ordering |
| 31 | + |
| 32 | +The `FT.AGGREGATE` command processes multiple expressions in a pipeline. Below is the recommended order: |
| 33 | + |
| 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. |
| 36 | +1. `FILTER` – filters raw documents before transformations or aggregation. |
| 37 | +1. `LOAD` – loads document fields. |
| 38 | +1. `APPLY` – applies transformations on fields. |
| 39 | +1. `GROUPBY` – groups results by specific fields. |
| 40 | +1. `REDUCE` – performs aggregations. For example, `SUM`, `COUNT`, and `AVG`. |
| 41 | +1. `SORTBY` – orders the results based on specified fields. |
| 42 | +1. `LIMIT` – restricts the number of results returned. |
| 43 | +1. `DIALECT 2` - provides for more comprehensive query 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 `@` |
| 48 | + |
| 49 | +You must add `@` at the start of a field name 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 with `@`. |
| 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 | + 1) (integer) 200 |
| 61 | + 2) 1) "price" |
| 62 | + 2) "623" |
| 63 | + 3) "adjusted_price" |
| 64 | + 4) "685.3" |
| 65 | + 3) 1) "price" |
| 66 | + 2) "619.75" |
| 67 | + 3) "adjusted_price" |
| 68 | + 4) "681.725" |
| 69 | + . |
| 70 | + . |
| 71 | + . |
| 72 | +``` |
| 73 | + |
| 74 | +- When referencing fields inside a `FILTER` clause that were loaded from documents. |
| 75 | + |
| 76 | +```sh |
| 77 | +FT.AGGREGATE products "*" |
| 78 | + LOAD 1 @rating |
| 79 | + FILTER "@rating >= 4.5" |
| 80 | + LIMIT 0 10 |
| 81 | + |
| 82 | + 1) (integer) 5 |
| 83 | + 2) 1) "rating" |
| 84 | + 2) "4.5" |
| 85 | + 3) 1) "rating" |
| 86 | + 2) "4.8" |
| 87 | + 4) 1) "rating" |
| 88 | + 2) "4.5" |
| 89 | + . |
| 90 | + . |
| 91 | + . |
| 92 | +``` |
| 93 | + |
| 94 | +- When referencing fields inside `GROUPBY` or `REDUCE` clauses. |
| 95 | + |
| 96 | +```sh |
| 97 | +FT.AGGREGATE products "*" |
| 98 | + GROUPBY 1 @category |
| 99 | + REDUCE SUM 1 @price AS total_price |
| 100 | + LIMIT 0 10 |
| 101 | + |
| 102 | +1) (integer) 6 |
| 103 | +2) 1) "category" |
| 104 | + 2) "Toys" |
| 105 | + 3) "total_price" |
| 106 | + 4) "9799.25" |
| 107 | +3) 1) "category" |
| 108 | + 2) "Electronics" |
| 109 | + 3) "total_price" |
| 110 | + 4) "10683.5" |
| 111 | +4) 1) "category" |
| 112 | + 2) "Apparel" |
| 113 | + 3) "total_price" |
| 114 | + 4) "10273.5" |
| 115 | + . |
| 116 | + . |
| 117 | + . |
| 118 | +``` |
| 119 | + |
| 120 | +- When referencing fields created by `REDUCE` in `APPLY` or `FILTER` clauses. |
| 121 | + |
| 122 | +```sh |
| 123 | +FT.AGGREGATE products "*" |
| 124 | + GROUPBY 1 @category |
| 125 | + REDUCE SUM 1 @price AS total_price |
| 126 | + APPLY "@total_price * 1.2" AS boosted_price |
| 127 | + FILTER "@total_price > 1000" |
| 128 | + LIMIT 0 10 |
| 129 | + |
| 130 | +1) (integer) 6 |
| 131 | +2) 1) "category" |
| 132 | + 2) "Toys" |
| 133 | + 3) "total_price" |
| 134 | + 4) "9799.25" |
| 135 | + 5) "boosted_price" |
| 136 | + 6) "11759.1" |
| 137 | +3) 1) "category" |
| 138 | + 2) "Electronics" |
| 139 | + 3) "total_price" |
| 140 | + 4) "10683.5" |
| 141 | + 5) "boosted_price" |
| 142 | + 6) "12820.2" |
| 143 | + . |
| 144 | + . |
| 145 | + . |
| 146 | +``` |
| 147 | + |
| 148 | +- When referencing fields created by `APPLY` in another `APPLY` or `FILTER` clause. |
| 149 | + |
| 150 | +```sh |
| 151 | +FT.AGGREGATE products "*" |
| 152 | + LOAD 2 @price @discount |
| 153 | + APPLY "@price - @discount" AS net_price |
| 154 | + APPLY "@net_price * 1.1" AS marked_up |
| 155 | + FILTER "@net_price > 200" |
| 156 | + LIMIT 0 10 |
| 157 | + |
| 158 | +1) (integer) 60 |
| 159 | +2) 1) "price" |
| 160 | + 2) "220" |
| 161 | + 3) "discount" |
| 162 | + 4) "0" |
| 163 | + 5) "net_price" |
| 164 | + 6) "220" |
| 165 | + 7) "marked_up" |
| 166 | + 8) "242" |
| 167 | +3) 1) "price" |
| 168 | + 2) "223.25" |
| 169 | + 3) "discount" |
| 170 | + 4) "1.5" |
| 171 | + 5) "net_price" |
| 172 | + 6) "221.75" |
| 173 | + 7) "marked_up" |
| 174 | + 8) "243.925" |
| 175 | + . |
| 176 | + . |
| 177 | + . |
| 178 | +``` |
| 179 | + |
| 180 | +- When referencing fields created by `APPLY` in a `SORTBY` clause. |
| 181 | + |
| 182 | +```sh |
| 183 | +FT.AGGREGATE products "*" |
| 184 | + LOAD 2 @price @discount |
| 185 | + APPLY "@price - @discount" AS net_price |
| 186 | + SORTBY 2 @net_price DESC |
| 187 | + LIMIT 0 10 |
| 188 | + |
| 189 | + 1) (integer) 200 |
| 190 | + 2) 1) "price" |
| 191 | + 2) "623" |
| 192 | + 3) "discount" |
| 193 | + 4) "6" |
| 194 | + 5) "net_price" |
| 195 | + 6) "617" |
| 196 | + 3) 1) "price" |
| 197 | + 2) "619.75" |
| 198 | + 3) "discount" |
| 199 | + 4) "4.5" |
| 200 | + 5) "net_price" |
| 201 | + 6) "615.25" |
| 202 | + . |
| 203 | + . |
| 204 | + . |
| 205 | +``` |
| 206 | + |
| 207 | +## GROUPBY with multiple REDUCE operations |
| 208 | + |
| 209 | +You can use multiple `REDUCE` operations after `GROUPBY` for different aggregations. |
| 210 | + |
| 211 | +```sh |
| 212 | +FT.AGGREGATE products "*" |
| 213 | + GROUPBY 1 @category |
| 214 | + REDUCE COUNT 0 AS product_count |
| 215 | + REDUCE SUM 1 @price AS total_price |
| 216 | + REDUCE AVG 1 @rating AS avg_rating |
| 217 | + SORTBY 2 @total_price DESC |
| 218 | + LIMIT 0 10 |
| 219 | + |
| 220 | +1) (integer) 6 |
| 221 | +2) 1) "category" |
| 222 | + 2) "Groceries" |
| 223 | + 3) "product_count" |
| 224 | + 4) "44" |
| 225 | + 5) "total_price" |
| 226 | + 6) "13495.25" |
| 227 | + 7) "avg_rating" |
| 228 | + 8) "3.94090909091" |
| 229 | +3) 1) "category" |
| 230 | + 2) "Home" |
| 231 | + 3) "product_count" |
| 232 | + 4) "40" |
| 233 | + 5) "total_price" |
| 234 | + 6) "11026.75" |
| 235 | + 7) "avg_rating" |
| 236 | + 8) "3.78" |
| 237 | + . |
| 238 | + . |
| 239 | + . |
| 240 | +``` |
| 241 | + |
| 242 | +## Multiple APPLY operations followed by GROUPBY and REDUCE |
| 243 | + |
| 244 | +You can use `APPLY` in various ways before and after `GROUPBY` and `REDUCE`. |
| 245 | + |
| 246 | +```sh |
| 247 | +FT.AGGREGATE products "*" |
| 248 | + LOAD 3 @price @discount @quantity |
| 249 | + APPLY "@price - @discount" AS final_price |
| 250 | + APPLY "@final_price * @quantity" AS total_revenue |
| 251 | + GROUPBY 1 @category |
| 252 | + REDUCE SUM 1 @total_revenue AS total_category_revenue |
| 253 | + SORTBY 2 @total_category_revenue DESC |
| 254 | + LIMIT 0 10 |
| 255 | + |
| 256 | +1) (integer) 6 |
| 257 | +2) 1) "category" |
| 258 | + 2) "Groceries" |
| 259 | + 3) "total_category_revenue" |
| 260 | + 4) "81373" |
| 261 | +3) 1) "category" |
| 262 | + 2) "Home" |
| 263 | + 3) "total_category_revenue" |
| 264 | + 4) "55797.5" |
| 265 | + . |
| 266 | + . |
| 267 | + . |
| 268 | +``` |
| 269 | + |
| 270 | +## FILTER and PARAMS |
| 271 | + |
| 272 | +Use `FILTER` to remove unwanted records, and `PARAMS` to pass values to parameterized queries. |
| 273 | + |
| 274 | +```sh |
| 275 | +FT.AGGREGATE products "*" |
| 276 | + LOAD 3 @price @rating @quantity |
| 277 | + FILTER "@price >= 500" |
| 278 | + FILTER "@rating >= 4.0" |
| 279 | + APPLY "@price * @quantity" AS total_value |
| 280 | + SORTBY 2 @total_value DESC |
| 281 | + LIMIT 0 10 |
| 282 | + DIALECT 2 |
| 283 | + |
| 284 | +1) (integer) 200 |
| 285 | + 2) 1) "price" |
| 286 | + 2) "606.75" |
| 287 | + 3) "rating" |
| 288 | + 4) "4.2" |
| 289 | + 5) "quantity" |
| 290 | + 6) "10" |
| 291 | + 7) "total_value" |
| 292 | + 8) "6067.5" |
| 293 | + 3) 1) "price" |
| 294 | + 2) "541.75" |
| 295 | + 3) "rating" |
| 296 | + 4) "4.5" |
| 297 | + 5) "quantity" |
| 298 | + 6) "10" |
| 299 | + 7) "total_value" |
| 300 | + 8) "5417.5" |
| 301 | + . |
| 302 | + . |
| 303 | + . |
| 304 | +``` |
| 305 | + |
| 306 | +## Placement of FILTER before and after GROUPBY/APPLY |
| 307 | + |
| 308 | +- **Before GROUPBY:** Removes records before aggregation. |
| 309 | +- **After GROUPBY:** Filters based on aggregated results. |
| 310 | + |
| 311 | +## LOAD after GROUPBY/REDUCE |
| 312 | + |
| 313 | +This is not allowed and you'll get a syntax error. |
| 314 | + |
| 315 | +## Placement rules for specific parameters |
| 316 | + |
| 317 | +| Parameter | Placement | |
| 318 | +|----- |----- | |
| 319 | +| `TIMEOUT` | Can be placed anywhere. | |
| 320 | +| `LIMIT` | Must be at the end, before `DIALECT`. | |
| 321 | +| `WITHCURSOR` | Must be at the end, before `DIALECT`. | |
| 322 | +| `SCORER` | Must be placed between the query and pipeline operations. | |
| 323 | +| `ADDSCORES` | Must be placed between the query and pipeline operations. | |
| 324 | +| `DIALECT` | Must be at the end. | |
| 325 | + |
| 326 | +## LIMIT and WITHCURSOR used together |
| 327 | + |
| 328 | +While you wouldn't ordinarily use `LIMIT` and `WITHCURSOR` together in the same query, you can do so if necessary. |
| 329 | +`LIMIT`, as the name suggests, will limit the total number of results returned for the given query. `WITHCURSOR` will paginate the results in chunks of size `COUNT`. You can use the [cursor API]({{< relref "/develop/ai/search-and-query/advanced-concepts/aggregations/#cursor-api" >}}) to retrieve more results, as shown below. |
| 330 | + |
| 331 | +```sh |
| 332 | +FT.AGGREGATE products "*" |
| 333 | + GROUPBY 1 @category |
| 334 | + REDUCE COUNT 0 AS product_count |
| 335 | + LIMIT 0 100 |
| 336 | + WITHCURSOR COUNT 3 |
| 337 | + |
| 338 | +1) 1) (integer) 6 |
| 339 | + 2) 1) "category" |
| 340 | + 2) "Toys" |
| 341 | + 3) "product_count" |
| 342 | + 4) "28" |
| 343 | + 3) 1) "category" |
| 344 | + 2) "Electronics" |
| 345 | + 3) "product_count" |
| 346 | + 4) "31" |
| 347 | + 4) 1) "category" |
| 348 | + 2) "Apparel" |
| 349 | + 3) "product_count" |
| 350 | + 4) "36" |
| 351 | +2) (integer) 89400486 |
| 352 | +127.0.0.1:6379> FT.CURSOR READ products 89400486 COUNT 3 |
| 353 | +1) 1) (integer) 0 |
| 354 | + 2) 1) "category" |
| 355 | + 2) "Home" |
| 356 | + 3) "product_count" |
| 357 | + 4) "40" |
| 358 | + 3) 1) "category" |
| 359 | + 2) "Groceries" |
| 360 | + 3) "product_count" |
| 361 | + 4) "44" |
| 362 | + 4) 1) "category" |
| 363 | + 2) "Books" |
| 364 | + 3) "product_count" |
| 365 | + 4) "21" |
| 366 | +2) (integer) 89400486 |
| 367 | + . |
| 368 | + . |
| 369 | + . |
| 370 | +``` |
| 371 | + |
| 372 | +See the following resources for more information: |
| 373 | + |
| 374 | +- [Aggregations]({{< relref "/develop/ai/search-and-query/advanced-concepts/aggregations" >}}) discussion page. |
| 375 | +- [`FT.AGGREGATE` command page](https://redis.io/docs/latest/commands/ft.aggregate/) |
| 376 | +- [RQE source code](https://github.com/RediSearch/RediSearch/tree/master/src/aggregate) |
0 commit comments