{
  "schema_version": 2,
  "id": "develop/ai/search-and-query/query/aggregation",
  "title": "Aggregation queries",
  "url": "https://redis.io/docs/latest/develop/ai/search-and-query/query/aggregation/",
  "summary": "Group and aggregate query results",
  "aliases": [
    "/develop/interact/search-and-query/query/aggregation"
  ],
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-09-11T15:33:04+01:00",
  "page_type": "content",
  "content_hash": "7b05674fc750cff8fa0c725378797bc50573967229d57395d80c1ef759f6fc44",
  "sections": [
    {
      "id": "simple-mapping",
      "title": "Simple mapping",
      "role": "content",
      "text": "The `APPLY` clause allows you to apply a simple mapping function to a result set that is returned based on the query expression.\n\n[code example]\n\nHere is a more detailed explanation of the query syntax:\n\n1. **Query expression**: you can use the same query expressions as you would use with the [`FT.SEARCH`](https://redis.io/docs/latest/commands/ft.search) command. You can substitute `query_expr` with any of the expressions explained in the articles of this [query topic](https://redis.io/docs/latest/develop/ai/search-and-query/query). Vector search queries are an exception. You can't combine a vector search with an aggregation query.\n2. **Loaded fields**: if field values weren't already loaded into the aggregation pipeline, you can force their presence via the `LOAD` clause. This clause takes the number of fields (`n`), followed by the field names (`\"field_1\" .. \"field_n\"`).\n3. **Mapping function**: this mapping function operates on the field values. A specific field is referenced as `@field_name` within the function expression. The result is returned as `result_field`.\n\nThe following example shows you how to calculate a discounted price for new bicycles:\n\nFoundational: Apply mapping functions to transform field values using APPLY when you need to calculate derived values\n\n**Difficulty:** Beginner\n\n**Available in:** Redis CLI, C#, Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python\n\n##### Redis CLI\n\n[code example]\n\n##### C#\n\n[code example]\n\n##### Go\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]\n\n##### Java (Synchronous - Jedis)\n\n[code example]\n\n##### JavaScript (Node.js)\n\n[code example]\n\n##### Python\n\n[code example]\n\n\n\nThe field `__key` is a built-in field. \n\nThe output of this query is:\n\n[code example]"
    },
    {
      "id": "grouping-with-aggregation",
      "title": "Grouping with aggregation",
      "role": "content",
      "text": "The previous example did not group the data. You can group and aggregate data based on one or many criteria in the following way:\n\n[code example]\n\nHere is an explanation of the additional constructs:\n\n1. **Grouping**: you can group by one or many fields. Each ordered sequence of field values then defines one group. It's also possible to group by values that resulted from a previous `APPLY ... AS`.\n2. **Aggregation**: you must replace `AGG_FUNC` with one of the supported aggregation functions (e.g., `SUM` or `COUNT`). A complete list of functions is available in the [aggregations reference documentation](https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/aggregations). Replace `aggregated_result_field` with a value of your choice.\n\nThe following query shows you how to group by the field `condition` and apply a reduction based on the previously derived `price_category`. The expression `@price<1000` causes a bicycle to have the price category `1` if its price is lower than 1000 USD. Otherwise, it has the price category `0`. The output is the number of affordable bicycles grouped by price category.\n\nGrouping with aggregation: Use GROUPBY with REDUCE to group results by field values and apply aggregation functions when you need to summarize data across groups\n\n**Difficulty:** Intermediate\n\n**Available in:** Redis CLI, C#, Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python\n\n##### Redis CLI\n\n[code example]\n\n##### C#\n\n[code example]\n\n##### Go\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]\n\n##### Java (Synchronous - Jedis)\n\n[code example]\n\n##### JavaScript (Node.js)\n\n[code example]\n\n##### Python\n\n[code example]\n\n\n\n[code example]\n\n> [!NOTE]\n> You can also create more complex aggregation pipelines with [FT.AGGREGATE](https://redis.io/docs/latest/commands/ft.aggregate). Applying multiple reduction functions under one `GROUPBY` clause is possible. In addition, you can also chain groupings and mix in additional mapping steps (e.g., `GROUPBY ... REDUCE ... APPLY ... GROUPBY ... REDUCE`)"
    },
    {
      "id": "aggregating-without-grouping",
      "title": "Aggregating without grouping",
      "role": "content",
      "text": "You can't use an aggregation function outside of a `GROUPBY` clause, but you can construct your pipeline in a way that the aggregation happens on a single group that spans all documents. If your documents don't share a common attribute, you can add it via an extra `APPLY` step.\n\nHere is an example that adds a type attribute `bicycle` to each document before counting all documents with that type:\n\nAggregating without grouping: Create a dummy grouping field with APPLY to aggregate across all documents when you need a single aggregate result\n\n**Difficulty:** Intermediate\n\n**Available in:** Redis CLI, C#, Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python\n\n##### Redis CLI\n\n[code example]\n\n##### C#\n\n[code example]\n\n##### Go\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]\n\n##### Java (Synchronous - Jedis)\n\n[code example]\n\n##### JavaScript (Node.js)\n\n[code example]\n\n##### Python\n\n[code example]\n\n\n\nThe result is:\n\n[code example]"
    },
    {
      "id": "grouping-without-aggregation",
      "title": "Grouping without aggregation",
      "role": "content",
      "text": "It's sometimes necessary to group your data without applying a mathematical aggregation function. If you need a grouped list of values, then the `TOLIST` function is helpful.\n\nThe following example shows how to group all bicycles by `condition`:\n\nGrouping without aggregation: Use TOLIST to collect grouped values without applying mathematical functions when you need to organize results by category\n\n**Difficulty:** Intermediate\n\n**Available in:** Redis CLI, C#, Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python\n\n##### Redis CLI\n\n[code example]\n\n##### C#\n\n[code example]\n\n##### Go\n\n[code example]\n\n##### Java (Asynchronous - Lettuce)\n\n[code example]\n\n##### Java (Reactive - Lettuce)\n\n[code example]\n\n##### Java (Synchronous - Jedis)\n\n[code example]\n\n##### JavaScript (Node.js)\n\n[code example]\n\n##### Python\n\n[code example]\n\n\n\nThe output of this query is:\n\n[code example]"
    }
  ],
  "examples": [
    {
      "id": "simple-mapping-ex0",
      "language": "plaintext",
      "code": "FT.AGGREGATE index \"query_expr\" LOAD n \"field_1\" .. \"field_n\" APPLY \"function_expr\" AS \"result_field\"",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex1",
      "language": "plaintext",
      "code": "FT.AGGREGATE idx:bicycle \"@condition:{new}\" LOAD 2 \"__key\" \"price\" APPLY \"@price - (@price * 0.1)\" AS \"discounted\"",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex2",
      "language": "csharp",
      "code": "AggregationResult res1 = db.FT().Aggregate(\n            \"idx:bicycle\",\n            new AggregationRequest(\"@condition:{new}\")\n                .Load(new FieldName(\"__key\"), new FieldName(\"price\"))\n                .Apply(\"@price - (@price * 0.1)\", \"discounted\")\n        );\n        Console.WriteLine(res1.TotalResults);   // >>> 5\n\n        for (int i = 0; i < res1.TotalResults; i++)\n        {\n            Row res1Row = res1.GetRow(i);\n\n            Console.WriteLine(\n                $\"Key: {res1Row[\"__key\"]}, Price: {res1Row[\"price\"]}, Discounted: {res1Row[\"discounted\"]}\"\n            );\n        }\n        // >>> Key: bicycle:0, Price: 270, Discounted: 243\n        // >>> Key: bicycle:5, Price: 810, Discounted: 729\n        // >>> Key: bicycle:6, Price: 2300, Discounted: 2070\n        // >>> Key: bicycle:7, Price: 430, Discounted: 387\n        // >>> Key: bicycle:8, Price: 1200, Discounted: 1080",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex3",
      "language": "go",
      "code": "res1, err := rdb.FTAggregateWithArgs(ctx,\n\t\t\"idx:bicycle\",\n\t\t\"@condition:{new}\",\n\t\t&redis.FTAggregateOptions{\n\t\t\tSteps: []redis.FTAggregateStep{\n\t\t\t\t{Load: &redis.FTAggregateLoad{Field: \"__key\"}},\n\t\t\t\t{Load: &redis.FTAggregateLoad{Field: \"price\"}},\n\t\t\t\t{Apply: &redis.FTAggregateApply{\n\t\t\t\t\tField: \"@price - (@price * 0.1)\",\n\t\t\t\t\tAs:    \"discounted\",\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(len(res1.Rows)) // >>> 5\n\n\tsort.Slice(res1.Rows, func(i, j int) bool {\n\t\treturn res1.Rows[i].Fields[\"__key\"].(string) <\n\t\t\tres1.Rows[j].Fields[\"__key\"].(string)\n\t})\n\n\tfor _, row := range res1.Rows {\n\t\tfmt.Printf(\n\t\t\t\"__key=%v, discounted=%v, price=%v\\n\",\n\t\t\trow.Fields[\"__key\"],\n\t\t\trow.Fields[\"discounted\"],\n\t\t\trow.Fields[\"price\"],\n\t\t)\n\t}\n\t// >>> __key=bicycle:0, discounted=243, price=270\n\t// >>> __key=bicycle:5, discounted=729, price=810\n\t// >>> __key=bicycle:6, discounted=2070, price=2300\n\t// >>> __key=bicycle:7, discounted=387, price=430\n\t// >>> __key=bicycle:8, discounted=1080, price=1200",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex4",
      "language": "java",
      "code": "AggregateArgs<String, String> agg1Args = AggregateArgs.<String, String> builder().load(\"__key\").load(\"price\")\n                    .apply(\"@price - (@price * 0.1)\", \"discounted\").build();\n\n            CompletableFuture<AggregationReply<String, String>> agg1 = asyncCommands\n                    .ftAggregate(\"idx:bicycle\", \"@condition:{new}\", agg1Args).thenApply(result -> {\n                        result.getReplies().get(0).getResults().stream()\n                                .sorted((doc1, doc2) -> doc1.getFields().get(\"__key\").compareTo(doc2.getFields().get(\"__key\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Key: %s, Price: %s, Discounted: %s\\n\", doc.getFields().get(\"__key\"),\n                                            doc.getFields().get(\"price\"), doc.getFields().get(\"discounted\"));\n                                });\n                        // >>> Key: bicycle:0, Price: 270, Discounted: 243\n                        // >>> Key: bicycle:5, Price: 810, Discounted: 729\n                        // >>> Key: bicycle:6, Price: 2300, Discounted: 2070\n                        // >>> Key: bicycle:7, Price: 430, Discounted: 387\n                        // >>> Key: bicycle:8, Price: 1200, Discounted: 1080\n                        return result;\n                    }).toCompletableFuture();",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex5",
      "language": "java",
      "code": "AggregateArgs<String, String> agg1Args = AggregateArgs.<String, String> builder().load(\"__key\").load(\"price\")\n                    .apply(\"@price - (@price * 0.1)\", \"discounted\").build();\n\n            Mono<AggregationReply<String, String>> agg1 = reactiveCommands\n                    .ftAggregate(\"idx:bicycle\", \"@condition:{new}\", agg1Args).doOnNext(result -> {\n                        result.getReplies().get(0).getResults().stream()\n                                .sorted((doc1, doc2) -> doc1.getFields().get(\"__key\").compareTo(doc2.getFields().get(\"__key\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Key: %s, Price: %s, Discounted: %s\\n\", doc.getFields().get(\"__key\"),\n                                            doc.getFields().get(\"price\"), doc.getFields().get(\"discounted\"));\n                                });\n                        // >>> Key: bicycle:0, Price: 270, Discounted: 243\n                        // >>> Key: bicycle:5, Price: 810, Discounted: 729\n                        // >>> Key: bicycle:6, Price: 2300, Discounted: 2070\n                        // >>> Key: bicycle:7, Price: 430, Discounted: 387\n                        // >>> Key: bicycle:8, Price: 1200, Discounted: 1080\n                    });",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex6",
      "language": "java",
      "code": "AggregationResult res1 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"@condition:{new}\")\n                    .load(\"__key\", \"price\")\n                    .apply(\"@price - (@price * 0.1)\", \"discounted\")\n        );\n        \n        List<Row> rows1 = res1.getRows();\n        System.out.println(rows1.size());   // >>> 5\n\n        for (int i = 0; i < rows1.size(); i++) {\n            System.out.println(rows1.get(i));\n        }\n        // >>> {__key=bicycle:0, discounted=243, price=270}\n        // >>> {__key=bicycle:5, discounted=729, price=810}\n        // >>> {__key=bicycle:6, discounted=2070, price=2300}\n        // >>> {__key=bicycle:7, discounted=387, price=430}\n        // >>> {__key=bicycle:8, discounted=1080, price=1200}",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex7",
      "language": "javascript",
      "code": "const res1 = await client.ft.aggregate('idx:bicycle', '@condition:{new}', {\n  LOAD: ['__key', 'price'],\n  APPLY: {\n    expression: '@price - (@price * 0.1)',\n    AS: 'discounted'\n  }\n});\n\nconsole.log(res1.results.length); // >>> 5\nconsole.log(res1.results); // >>>\n//[\n//  [Object: null prototype] { __key: 'bicycle:0', price: '270' },\n//  [Object: null prototype] { __key: 'bicycle:5', price: '810' },\n//  [Object: null prototype] { __key: 'bicycle:6', price: '2300' },\n//  [Object: null prototype] { __key: 'bicycle:7', price: '430' },\n//  [Object: null prototype] { __key: 'bicycle:8', price: '1200' }\n//]",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex8",
      "language": "python",
      "code": "search = Search(r, index_name=\"idx:bicycle\")\naggregate_request = AggregateRequest(query='@condition:{new}') \\\n    .load('__key', 'price') \\\n    .apply(discounted='@price - (@price * 0.1)')\nres = search.aggregate(aggregate_request)\nprint(len(res.rows)) # >>> 5\nprint(res.rows) # >>> [['__key', 'bicycle:0', ...\n#[['__key', 'bicycle:0', 'price', '270', 'discounted', '243'],\n# ['__key', 'bicycle:5', 'price', '810', 'discounted', '729'],\n# ['__key', 'bicycle:6', 'price', '2300', 'discounted', '2070'],\n# ['__key', 'bicycle:7', 'price', '430', 'discounted', '387'],\n# ['__key', 'bicycle:8', 'price', '1200', 'discounted', '1080']]",
      "section_id": "simple-mapping"
    },
    {
      "id": "simple-mapping-ex9",
      "language": "plaintext",
      "code": "1) \"1\"\n2) 1) \"__key\"\n   1) \"bicycle:0\"\n   2) \"price\"\n   3) \"270\"\n   4) \"discounted\"\n   5) \"243\"\n3) 1) \"__key\"\n   1) \"bicycle:5\"\n   2) \"price\"\n   3) \"810\"\n   4) \"discounted\"\n   5) \"729\"\n4) 1) \"__key\"\n   1) \"bicycle:6\"\n   2) \"price\"\n   3) \"2300\"\n   4) \"discounted\"\n   5) \"2070\"\n...",
      "section_id": "simple-mapping"
    },
    {
      "id": "grouping-with-aggregation-ex0",
      "language": "plaintext",
      "code": "FT.AGGREGATE index \"query_expr\" ...  GROUPBY n \"field_1\" .. \"field_n\" REDUCE AGG_FUNC m \"@field_param_1\" .. \"@field_param_m\" AS \"aggregated_result_field\"",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex1",
      "language": "plaintext",
      "code": "FT.AGGREGATE idx:bicycle \"*\" LOAD 1 price APPLY \"@price<1000\" AS price_category GROUPBY 1 @condition REDUCE SUM 1 \"@price_category\" AS \"num_affordable\"",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex2",
      "language": "csharp",
      "code": "AggregationResult res2 = db.FT().Aggregate(\n            \"idx:bicycle\",\n            new AggregationRequest(\"*\")\n                .Load(new FieldName(\"price\"))\n                .Apply(\"@price<1000\", \"price_category\")\n                .GroupBy(\n                    \"@condition\",\n                    Reducers.Sum(\"@price_category\").As(\"num_affordable\")\n                )\n        );\n        Console.WriteLine(res2.TotalResults);   // >>> 3\n\n        for (int i = 0; i < res2.TotalResults; i++)\n        {\n            Row res2Row = res2.GetRow(i);\n\n            Console.WriteLine(\n                $\"Condition: {res2Row[\"condition\"]}, Num affordable: {res2Row[\"num_affordable\"]}\"\n            );\n        }\n        // >>> Condition: refurbished, Num affordable: 1\n        // >>> Condition: used, Num affordable: 1\n        // >>> Condition: new, Num affordable: 3",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex3",
      "language": "go",
      "code": "res2, err := rdb.FTAggregateWithArgs(ctx,\n\t\t\"idx:bicycle\", \"*\",\n\t\t&redis.FTAggregateOptions{\n\t\t\tSteps: []redis.FTAggregateStep{\n\t\t\t\t{Load: &redis.FTAggregateLoad{Field: \"price\"}},\n\t\t\t\t{Apply: &redis.FTAggregateApply{\n\t\t\t\t\tField: \"@price<1000\",\n\t\t\t\t\tAs:    \"price_category\",\n\t\t\t\t}},\n\t\t\t\t{GroupBy: &redis.FTAggregateGroupBy{\n\t\t\t\t\tFields: []interface{}{\"@condition\"},\n\t\t\t\t\tReduce: []redis.FTAggregateReducer{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tReducer: redis.SearchSum,\n\t\t\t\t\t\t\tArgs:    []interface{}{\"@price_category\"},\n\t\t\t\t\t\t\tAs:      \"num_affordable\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(len(res2.Rows)) // >>> 3\n\n\tsort.Slice(res2.Rows, func(i, j int) bool {\n\t\treturn res2.Rows[i].Fields[\"condition\"].(string) <\n\t\t\tres2.Rows[j].Fields[\"condition\"].(string)\n\t})\n\n\tfor _, row := range res2.Rows {\n\t\tfmt.Printf(\n\t\t\t\"condition=%v, num_affordable=%v\\n\",\n\t\t\trow.Fields[\"condition\"],\n\t\t\trow.Fields[\"num_affordable\"],\n\t\t)\n\t}\n\t// >>> condition=new, num_affordable=3\n\t// >>> condition=refurbished, num_affordable=1\n\t// >>> condition=used, num_affordable=1",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex4",
      "language": "java",
      "code": "AggregateArgs<String, String> agg2Args = AggregateArgs.<String, String> builder().load(\"price\")\n                    .apply(\"@price<1000\", \"price_category\").groupBy(GroupBy.<String, String> of(\"condition\")\n                            .reduce(Reducer.<String, String> sum(\"@price_category\").as(\"num_affordable\")))\n                    .build();\n\n            CompletableFuture<AggregationReply<String, String>> agg2 = asyncCommands.ftAggregate(\"idx:bicycle\", \"*\", agg2Args)\n                    .thenApply(result -> {\n                        result.getReplies().get(0).getResults().stream().sorted(\n                                (doc1, doc2) -> doc1.getFields().get(\"condition\").compareTo(doc2.getFields().get(\"condition\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Condition: %s, Num Affordable: %s\\n\", doc.getFields().get(\"condition\"),\n                                            doc.getFields().get(\"num_affordable\"));\n                                });\n                        // >>> Condition: new, Num Affordable: 3\n                        // >>> Condition: refurbished, Num Affordable: 1\n                        // >>> Condition: used, Num Affordable: 1\n                        return result;\n                    }).toCompletableFuture();",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex5",
      "language": "java",
      "code": "AggregateArgs<String, String> agg2Args = AggregateArgs.<String, String> builder().load(\"price\")\n                    .apply(\"@price<1000\", \"price_category\").groupBy(GroupBy.<String, String> of(\"condition\")\n                            .reduce(Reducer.<String, String> sum(\"@price_category\").as(\"num_affordable\")))\n                    .build();\n\n            Mono<AggregationReply<String, String>> agg2 = reactiveCommands.ftAggregate(\"idx:bicycle\", \"*\", agg2Args)\n                    .doOnNext(result -> {\n                        result.getReplies().get(0).getResults().stream().sorted(\n                                (doc1, doc2) -> doc1.getFields().get(\"condition\").compareTo(doc2.getFields().get(\"condition\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Condition: %s, Num Affordable: %s\\n\", doc.getFields().get(\"condition\"),\n                                            doc.getFields().get(\"num_affordable\"));\n                                });\n                        // >>> Condition: new, Num Affordable: 3\n                        // >>> Condition: refurbished, Num Affordable: 1\n                        // >>> Condition: used, Num Affordable: 1\n                    });",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex6",
      "language": "java",
      "code": "AggregationResult res2 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .load(\"price\")\n                    .apply(\"@price<1000\", \"price_category\")\n                    .groupBy(\"@condition\",\n                        Reducers.sum(\"@price_category\").as(\"num_affordable\"))\n        );\n\n        List<Row> rows2 = res2.getRows();\n        System.out.println(rows2.size());   // >>> 3\n\n        for (int i = 0; i < rows2.size(); i++) {\n            System.out.println(rows2.get(i));\n        }\n        // >>> {condition=refurbished, num_affordable=1}\n        // >>> {condition=used, num_affordable=1}\n        // >>> {condition=new, num_affordable=3}",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex7",
      "language": "javascript",
      "code": "const res2 = await client.ft.aggregate('idx:bicycle', '*', {\n  LOAD: ['@price'],\n  STEPS: [{\n      type: FT_AGGREGATE_STEPS.APPLY,\n      expression: '@price<1000',\n      AS: 'price_category'\n    },{\n      type: FT_AGGREGATE_STEPS.GROUPBY,\n      properties: '@condition',\n      REDUCE:[{\n        type: FT_AGGREGATE_GROUP_BY_REDUCERS.SUM,\n        property: '@price_category',\n        AS: 'num_affordable'\n      }]\n    }]\n});\nconsole.log(res2.results.length); // >>> 3\nconsole.log(res2.results); // >>>\n//[[Object: null prototype] { condition: 'refurbished', num_affordable: '1' },\n//  [Object: null prototype] { condition: 'used', num_affordable: '1' },\n//  [Object: null prototype] { condition: 'new', num_affordable: '3' }\n//]",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex8",
      "language": "python",
      "code": "search = Search(r, index_name=\"idx:bicycle\")\naggregate_request = AggregateRequest(query='*') \\\n    .load('price') \\\n    .apply(price_category='@price<1000') \\\n    .group_by('@condition', reducers.sum('@price_category').alias('num_affordable'))\nres = search.aggregate(aggregate_request)\nprint(len(res.rows)) # >>> 3\nprint(res.rows) # >>>\n#[['condition', 'refurbished', 'num_affordable', '1'],\n# ['condition', 'used', 'num_affordable', '1'],\n# ['condition', 'new', 'num_affordable', '3']]",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "grouping-with-aggregation-ex9",
      "language": "plaintext",
      "code": "1) \"3\"\n2) 1) \"condition\"\n   1) \"refurbished\"\n   2) \"num_affordable\"\n   3) \"1\"\n3) 1) \"condition\"\n   1) \"used\"\n   2) \"num_affordable\"\n   3) \"1\"\n4) 1) \"condition\"\n   1) \"new\"\n   2) \"num_affordable\"\n   3) \"3\"",
      "section_id": "grouping-with-aggregation"
    },
    {
      "id": "aggregating-without-grouping-ex0",
      "language": "plaintext",
      "code": "FT.AGGREGATE idx:bicycle \"*\" APPLY \"'bicycle'\" AS type GROUPBY 1 @type REDUCE COUNT 0 AS num_total",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex1",
      "language": "csharp",
      "code": "AggregationResult res3 = db.FT().Aggregate(\n            \"idx:bicycle\",\n            new AggregationRequest(\"*\")\n                .Apply(\"'bicycle'\", \"type\")\n                .GroupBy(\"@type\", Reducers.Count().As(\"num_total\"))\n        );\n        Console.WriteLine(res3.TotalResults);   // >>> 1\n\n        Row res3Row = res3.GetRow(0);\n        Console.WriteLine($\"Type: {res3Row[\"type\"]}, Num total: {res3Row[\"num_total\"]}\");\n        // >>> Type: bicycle, Num total: 10",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex2",
      "language": "go",
      "code": "res3, err := rdb.FTAggregateWithArgs(ctx,\n\t\t\"idx:bicycle\", \"*\",\n\t\t&redis.FTAggregateOptions{\n\t\t\tSteps: []redis.FTAggregateStep{\n\t\t\t\t{Apply: &redis.FTAggregateApply{\n\t\t\t\t\tField: \"'bicycle'\",\n\t\t\t\t\tAs:    \"type\",\n\t\t\t\t}},\n\t\t\t\t{GroupBy: &redis.FTAggregateGroupBy{\n\t\t\t\t\tFields: []interface{}{\"@type\"},\n\t\t\t\t\tReduce: []redis.FTAggregateReducer{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tReducer: redis.SearchCount,\n\t\t\t\t\t\t\tAs:      \"num_total\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(len(res3.Rows)) // >>> 1\n\n\tfor _, row := range res3.Rows {\n\t\tfmt.Printf(\n\t\t\t\"type=%v, num_total=%v\\n\",\n\t\t\trow.Fields[\"type\"],\n\t\t\trow.Fields[\"num_total\"],\n\t\t)\n\t}\n\t// type=bicycle, num_total=10",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex3",
      "language": "java",
      "code": "AggregateArgs<String, String> agg3Args = AggregateArgs.<String, String> builder().apply(\"'bicycle'\", \"type\")\n                    .groupBy(GroupBy.<String, String> of(\"type\").reduce(Reducer.<String, String> count().as(\"num_total\")))\n                    .build();\n\n            CompletableFuture<AggregationReply<String, String>> agg3 = asyncCommands.ftAggregate(\"idx:bicycle\", \"*\", agg3Args)\n                    .thenApply(result -> {\n                        result.getReplies().get(0).getResults().stream()\n                                .sorted((doc1, doc2) -> doc1.getFields().get(\"type\").compareTo(doc2.getFields().get(\"type\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Type: %s, Total Count: %s\\n\", doc.getFields().get(\"type\"),\n                                            doc.getFields().get(\"num_total\"));\n                                });\n                        // >>> Type: bicycle, Total Count: 10\n                        return result;\n                    }).toCompletableFuture();",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex4",
      "language": "java",
      "code": "AggregateArgs<String, String> agg3Args = AggregateArgs.<String, String> builder().apply(\"'bicycle'\", \"type\")\n                    .groupBy(GroupBy.<String, String> of(\"type\").reduce(Reducer.<String, String> count().as(\"num_total\")))\n                    .build();\n\n            Mono<AggregationReply<String, String>> agg3 = reactiveCommands.ftAggregate(\"idx:bicycle\", \"*\", agg3Args)\n                    .doOnNext(result -> {\n                        result.getReplies().get(0).getResults().stream()\n                                .sorted((doc1, doc2) -> doc1.getFields().get(\"type\").compareTo(doc2.getFields().get(\"type\")))\n                                .forEach(doc -> {\n                                    System.out.printf(\"Type: %s, Total Count: %s\\n\", doc.getFields().get(\"type\"),\n                                            doc.getFields().get(\"num_total\"));\n                                });\n                        // >>> Type: bicycle, Total Count: 10\n                    });",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex5",
      "language": "java",
      "code": "AggregationResult res3 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .apply(\"'bicycle'\", \"type\")\n                    .groupBy(\"@type\", Reducers.count().as(\"num_total\"))\n        );\n\n        List<Row> rows3 = res3.getRows();\n        System.out.println(rows3.size());   // >>> 1\n\n        for (int i = 0; i < rows3.size(); i++) {\n            System.out.println(rows3.get(i));\n        }\n        // >>> {type=bicycle, num_total=10}",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex6",
      "language": "javascript",
      "code": "const res3 = await client.ft.aggregate('idx:bicycle', '*', {\n  STEPS: [{\n      type: FT_AGGREGATE_STEPS.APPLY,\n      expression: \"'bicycle'\",\n      AS: 'type'\n    }, {\n      type: FT_AGGREGATE_STEPS.GROUPBY,\n      properties: '@type',\n      REDUCE: [{\n        type: FT_AGGREGATE_GROUP_BY_REDUCERS.COUNT,\n        property: null,\n        AS: 'num_total'\n      }]\n    }]\n});\nconsole.log(res3.results.length); // >>> 1\nconsole.log(res3.results); // >>>\n//[ [Object: null prototype] { type: 'bicycle', num_total: '10' } ]",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex7",
      "language": "python",
      "code": "search = Search(r, index_name=\"idx:bicycle\")\naggregate_request = AggregateRequest(query='*') \\\n    .apply(type=\"'bicycle'\") \\\n    .group_by('@type', reducers.count().alias('num_total'))\nres = search.aggregate(aggregate_request)\nprint(len(res.rows)) # >>> 1\nprint(res.rows) # >>> [['type', 'bicycle', 'num_total', '10']]",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "aggregating-without-grouping-ex8",
      "language": "plaintext",
      "code": "1) \"1\"\n2) 1) \"type\"\n   1) \"bicycle\"\n   2) \"num_total\"\n   3) \"10\"",
      "section_id": "aggregating-without-grouping"
    },
    {
      "id": "grouping-without-aggregation-ex0",
      "language": "plaintext",
      "code": "FT.AGGREGATE idx:bicycle \"*\" LOAD 1 \"__key\" GROUPBY 1 \"@condition\" REDUCE TOLIST 1 \"__key\" AS bicycles",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex1",
      "language": "csharp",
      "code": "// Not supported in NRedisStack.",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex2",
      "language": "go",
      "code": "res4, err := rdb.FTAggregateWithArgs(ctx,\n\t\t\"idx:bicycle\", \"*\",\n\t\t&redis.FTAggregateOptions{\n\t\t\tSteps: []redis.FTAggregateStep{\n\t\t\t\t{Load: &redis.FTAggregateLoad{Field: \"__key\"}},\n\t\t\t\t{GroupBy: &redis.FTAggregateGroupBy{\n\t\t\t\t\tFields: []interface{}{\"@condition\"},\n\t\t\t\t\tReduce: []redis.FTAggregateReducer{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tReducer: redis.SearchToList,\n\t\t\t\t\t\t\tArgs:    []interface{}{\"__key\"},\n\t\t\t\t\t\t\tAs:      \"bicycles\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(len(res4.Rows)) // >>> 3\n\n\tsort.Slice(res4.Rows, func(i, j int) bool {\n\t\treturn res4.Rows[i].Fields[\"condition\"].(string) <\n\t\t\tres4.Rows[j].Fields[\"condition\"].(string)\n\t})\n\n\tfor _, row := range res4.Rows {\n\t\trowBikes := row.Fields[\"bicycles\"].([]interface{})\n\t\tbikes := make([]string, len(rowBikes))\n\n\t\tfor i, rowBike := range rowBikes {\n\t\t\tbikes[i] = rowBike.(string)\n\t\t}\n\n\t\tsort.Slice(bikes, func(i, j int) bool {\n\t\t\treturn bikes[i] < bikes[j]\n\t\t})\n\n\t\tfmt.Printf(\n\t\t\t\"condition=%v, bicycles=%v\\n\",\n\t\t\trow.Fields[\"condition\"],\n\t\t\tbikes,\n\t\t)\n\t}\n\t// >>> condition=new, bicycles=[bicycle:0 bicycle:5 bicycle:6 bicycle:7 bicycle:8]\n\t// >>> condition=refurbished, bicycles=[bicycle:9]\n\t// >>> condition=used, bicycles=[bicycle:1 bicycle:2 bicycle:3 bicycle:4]",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex3",
      "language": "java",
      "code": "// The `TOLIST` reducer is not currently available in Lettuce.",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex4",
      "language": "java",
      "code": "// The `TOLIST` reducer is not currently available in Lettuce.",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex5",
      "language": "java",
      "code": "AggregationResult res4 = jedis.ftAggregate(\"idx:bicycle\",\n            new AggregationBuilder(\"*\")\n                    .load(\"__key\")\n                    .groupBy(\"@condition\",\n                        Reducers.to_list(\"__key\").as(\"bicycles\"))\n        );\n\n        List<Row> rows4 = res4.getRows();\n        System.out.println(rows4.size());   // >>> 3\n\n        for (int i = 0; i < rows4.size(); i++) {\n            System.out.println(rows4.get(i));\n        }\n        // >>> {condition=refurbished, bicycles=[bicycle:9]}\n        // >>> {condition=used, bicycles=[bicycle:3, bicycle:4, bicycle:1, bicycle:2]}\n        // >>> {condition=new, bicycles=[bicycle:7, bicycle:0, bicycle:5, bicycle:6, bicycle:8]}",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex6",
      "language": "javascript",
      "code": "const res4 = await client.ft.aggregate('idx:bicycle', '*', {\n  LOAD: ['__key'],\n  STEPS: [{\n      type: FT_AGGREGATE_STEPS.GROUPBY,\n      properties: '@condition',\n      REDUCE: [{\n        type: FT_AGGREGATE_GROUP_BY_REDUCERS.TOLIST,\n        property: '__key',\n        AS: 'bicycles'\n      }]\n    }]\n});\nconsole.log(res4.results.length); // >>> 3\nconsole.log(res4.results); // >>>\n//[[Object: null prototype] {condition: 'refurbished', bicycles: [ 'bicycle:9' ]},\n//  [Object: null prototype] {condition: 'used', bicycles: [ 'bicycle:1', 'bicycle:2', 'bicycle:3', 'bicycle:4' ]},\n//  [Object: null prototype] {condition: 'new', bicycles: [ 'bicycle:5', 'bicycle:6', 'bicycle:7', 'bicycle:0', 'bicycle:8' ]}]",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex7",
      "language": "python",
      "code": "search = Search(r, index_name=\"idx:bicycle\")\naggregate_request = AggregateRequest(query='*') \\\n    .load('__key') \\\n    .group_by('@condition', reducers.tolist('__key').alias('bicycles'))\nres = search.aggregate(aggregate_request)\nprint(len(res.rows)) # >>> 3\nprint(res.rows) # >>>\n#[['condition', 'refurbished', 'bicycles', ['bicycle:9']],\n# ['condition', 'used', 'bicycles', ['bicycle:1', 'bicycle:2', 'bicycle:3', 'bicycle:4']],\n# ['condition', 'new', 'bicycles', ['bicycle:5', 'bicycle:6', 'bicycle:7', 'bicycle:0', 'bicycle:8']]]",
      "section_id": "grouping-without-aggregation"
    },
    {
      "id": "grouping-without-aggregation-ex8",
      "language": "plaintext",
      "code": "1) \"3\"\n2) 1) \"condition\"\n   1) \"refurbished\"\n   2) \"bicycles\"\n   3) 1) \"bicycle:9\"\n3) 1) \"condition\"\n   1) \"used\"\n   2) \"bicycles\"\n   3) 1) \"bicycle:1\"\n      1) \"bicycle:2\"\n      2) \"bicycle:3\"\n      3) \"bicycle:4\"\n4) 1) \"condition\"\n   1) \"new\"\n   2) \"bicycles\"\n   3) 1) \"bicycle:0\"\n      1) \"bicycle:5\"\n      2) \"bicycle:6\"\n      3) \"bicycle:8\"\n      4) \"bicycle:7\"",
      "section_id": "grouping-without-aggregation"
    }
  ]
}
