content/develop/ai/search-and-query/query/aggregation.md
An aggregation query allows you to perform the following actions:
This article explains the basic usage of the [FT.AGGREGATE]({{< relref "commands/ft.aggregate" >}}) command. For further details, see the [command specification]({{< relref "commands/ft.aggregate" >}}) and the [aggregations reference documentation]({{< relref "/develop/ai/search-and-query/advanced-concepts/aggregations" >}}).
The examples in this article use a schema with the following fields:
| Field name | Field type |
|---|---|
condition | TAG |
price | NUMERIC |
The APPLY clause allows you to apply a simple mapping function to a result set that is returned based on the query expression.
FT.AGGREGATE index "query_expr" LOAD n "field_1" .. "field_n" APPLY "function_expr" AS "result_field"
Here is a more detailed explanation of the query syntax:
FT.SEARCH]({{< relref "commands/ft.search/" >}}) command. You can substitute query_expr with any of the expressions explained in the articles of this [query topic]({{< relref "/develop/ai/search-and-query/query/" >}}). Vector search queries are an exception. You can't combine a vector search with an aggregation query.LOAD clause. This clause takes the number of fields (n), followed by the field names ("field_1" .. "field_n").@field_name within the function expression. The result is returned as result_field.The following example shows you how to calculate a discounted price for new bicycles:
{{< clients-example set="query_agg" step="agg1" description="Foundational: Apply mapping functions to transform field values using APPLY when you need to calculate derived values" difficulty="beginner" >}} FT.AGGREGATE idx:bicycle "@condition:{new}" LOAD 2 "__key" "price" APPLY "@price - (@price * 0.1)" AS "discounted" {{< /clients-example >}}
The field __key is a built-in field.
The output of this query is:
1) "1"
2) 1) "__key"
1) "bicycle:0"
2) "price"
3) "270"
4) "discounted"
5) "243"
3) 1) "__key"
1) "bicycle:5"
2) "price"
3) "810"
4) "discounted"
5) "729"
4) 1) "__key"
1) "bicycle:6"
2) "price"
3) "2300"
4) "discounted"
5) "2070"
...
The previous example did not group the data. You can group and aggregate data based on one or many criteria in the following way:
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"
Here is an explanation of the additional constructs:
APPLY ... AS.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]({{< relref "/develop/ai/search-and-query/advanced-concepts/aggregations" >}}). Replace aggregated_result_field with a value of your choice.The 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.
{{< clients-example set="query_agg" step="agg2" description="Grouping with aggregation: Use GROUPBY with REDUCE to group results by field values and apply aggregation functions when you need to summarize data across groups" difficulty="intermediate" >}} FT.AGGREGATE idx:bicycle "*" LOAD 1 price APPLY "@price<1000" AS price_category GROUPBY 1 @condition REDUCE SUM 1 "@price_category" AS "num_affordable" {{< /clients-example >}}
1) "3"
2) 1) "condition"
1) "refurbished"
2) "num_affordable"
3) "1"
3) 1) "condition"
1) "used"
2) "num_affordable"
3) "1"
4) 1) "condition"
1) "new"
2) "num_affordable"
3) "3"
{{% alert title="Note" color="warning" %}}
You can also create more complex aggregation pipelines with [FT.AGGREGATE]({{< relref "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)
{{% /alert %}}
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.
Here is an example that adds a type attribute bicycle to each document before counting all documents with that type:
{{< clients-example set="query_agg" step="agg3" description="Aggregating without grouping: Create a dummy grouping field with APPLY to aggregate across all documents when you need a single aggregate result" difficulty="intermediate" >}} FT.AGGREGATE idx:bicycle "*" APPLY "'bicycle'" AS type GROUPBY 1 @type REDUCE COUNT 0 AS num_total {{< /clients-example >}}
The result is:
1) "1"
2) 1) "type"
1) "bicycle"
2) "num_total"
3) "10"
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.
The following example shows how to group all bicycles by condition:
{{< clients-example set="query_agg" step="agg4" description="Grouping without aggregation: Use TOLIST to collect grouped values without applying mathematical functions when you need to organize results by category" difficulty="intermediate" >}} FT.AGGREGATE idx:bicycle "*" LOAD 1 "__key" GROUPBY 1 "@condition" REDUCE TOLIST 1 "__key" AS bicycles {{< /clients-example >}}
The output of this query is:
1) "3"
2) 1) "condition"
1) "refurbished"
2) "bicycles"
3) 1) "bicycle:9"
3) 1) "condition"
1) "used"
2) "bicycles"
3) 1) "bicycle:1"
1) "bicycle:2"
2) "bicycle:3"
3) "bicycle:4"
4) 1) "condition"
1) "new"
2) "bicycles"
3) 1) "bicycle:0"
1) "bicycle:5"
2) "bicycle:6"
3) "bicycle:8"
4) "bicycle:7"