> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SEARCH.AGGREGATE

> Compute analytics over matching documents.

Use `SEARCH.AGGREGATE` to compute metrics and buckets over matching documents.

The command takes two JSON arguments. The first is a filter, in the same language as [`SEARCH.QUERY`](/docs/redis/commands/search/search-query), which selects the documents to aggregate; pass `'{}'` to cover the whole index. The second describes the aggregations to compute, as named entries such as `{"avg_price": {"$avg": {"field": "price"}}}`.

Metric operators like `$avg`, `$sum`, `$min`, `$max`, `$stats`, and `$cardinality` reduce the selected documents to a single number, while bucket operators like `$terms`, `$range`, `$histogram`, and `$dateHistogram` group them and report a count per bucket. Bucket operators accept nested `$aggs`, so you can compute a metric inside each bucket, for example the average price per category, and several aggregations can be requested in one call since they all run over the same selected document set.

`$composite` buckets by several sources at once, producing one bucket per combination of their values, and pages through the result instead of returning it whole. Each entry in `sources` names a source and gives it a `$terms`, `$histogram`, or `$dateHistogram` definition over a `FAST` field. `size` sets the page size, defaulting to `10`, and the reply carries an `afterKey`; passing that value back as `after` returns the next page. This is how you enumerate a large cross-product of buckets, which `$terms` alone cannot do because it returns only its top terms.

By default `$sum` reports `0` for a bucket that matched no documents, matching Elasticsearch. Setting `"nullIfNoMatch": true` makes it report `null` instead, which separates "nothing matched" from a total that genuinely came to zero. It only affects empty buckets; a sum over matching documents is unchanged.

See [Aggregations](/docs/redis/search/aggregations) for the full operator reference and examples.

## Syntax

```redis theme={"system"}
SEARCH.AGGREGATE <name> '<json_filter>' '<json_aggregations>'
```

`<json_filter>` is an [Upstash JSON filter](/docs/redis/search/querying). The command accepts an index name or alias. See [Aggregations](/docs/redis/search/aggregations) for the aggregation object and supported operators.

## Response

In `redis-cli --json`, the response is an object keyed by aggregation alias:

```json theme={"system"}
{
  "avg_price": { "value": 49.99 },
  "by_category": { "buckets": [{ "key": "electronics", "docCount": 42 }] }
}
```

Raw RESP output may be rendered differently by client or protocol settings, but each top-level alias maps to its aggregation result. Returns `null` if the index does not exist.

A `$composite` aggregation replies with its buckets and the cursor to resume from:

```json theme={"system"}
{
  "by_cat_and_price": {
    "buckets": [
      { "key": { "by_cat": "books", "by_price": 20 }, "docCount": 1 }
    ],
    "afterKey": { "by_cat": "str:books", "by_price": "f64:20" }
  }
}
```

Pass the `afterKey` object back verbatim as the aggregation's `after` option to get the next page.

> [!NOTE]
>
> A boolean `false` in an aggregation result is encoded as `0` under RESP2 and over the REST API, and as a boolean under RESP3. Earlier versions encoded it as null under RESP2.

## Examples

> **Redis CLI**
>
> ```bash theme={"system"}
> SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}'
>
> # Page through every category and price bucket combination
> SEARCH.AGGREGATE products '{}' '{"by_cat_and_price": {"$composite": {"size": 100, "sources": [{"by_cat": {"$terms": {"field": "category"}}}, {"by_price": {"$histogram": {"field": "price", "interval": 10}}}]}}}'
> ```

> **@upstash/redis**
>
> ```ts theme={"system"}
> import { Redis } from "@upstash/redis";
>
> const redis = Redis.fromEnv();
> const products = redis.search.index({ name: "products" });
>
> const result = await products.aggregate({
>   aggregations: {
>     avg_price: { $avg: { field: "price" } },
>   },
> });
> ```

> **upstash\_redis**
>
> ```python theme={"system"}
> from upstash_redis import Redis
>
> redis = Redis.from_env()
> products = redis.search.index(name="products")
>
> result = products.aggregate(
>     aggregations={"avg_price": {"$avg": {"field": "price"}}},
> )
> ```

> **ioredis**
>
> ```ts theme={"system"}
> import IORedis from "ioredis";
> import { createSearch } from "@upstash/search-ioredis";
>
> const redis = new IORedis(process.env.REDIS_URL!);
> const search = createSearch(redis);
> const products = search.index({ name: "products" });
>
> const result = await products.aggregate({
>   aggregations: {
>     avg_price: { $avg: { field: "price" } },
>   },
> });
> ```

> **node-redis**
>
> ```ts theme={"system"}
> import { createClient } from "redis";
> import { createSearch } from "@upstash/search-redis";
>
> const client = await createClient({ url: process.env.REDIS_URL })
>   .on("error", console.error)
>   .connect();
> const search = createSearch(client);
> const products = search.index({ name: "products" });
>
> const result = await products.aggregate({
>   aggregations: {
>     avg_price: { $avg: { field: "price" } },
>   },
> });
> ```

> **redis-py**
>
> ```python theme={"system"}
> import os
> import redis
>
> client = redis.from_url(os.environ["REDIS_URL"])
> result = client.execute_command(
>     "SEARCH.AGGREGATE",
>     "products",
>     "{}",
>     "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}",
> )
> print(result)
> ```

> **go-redis**
>
> ```go theme={"system"}
> package main
>
> import (
>     "context"
>     "fmt"
>     "os"
>
>     "github.com/redis/go-redis/v9"
> )
>
> func main() {
>     opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
>     if err != nil {
>         panic(err)
>     }
>     client := redis.NewClient(opts)
>     result, err := client.Do(
>         context.Background(),
>         "SEARCH.AGGREGATE",
>         "products",
>         "{}",
>         "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}",
>     ).Result()
>     if err != nil {
>         panic(err)
>     }
>     fmt.Println(result)
> }
> ```

> **jedis**
>
> ```java theme={"system"}
> import java.net.URI;
>
> import redis.clients.jedis.Jedis;
>
> try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
>   Object result = jedis.sendCommand(
>       () -> "SEARCH.AGGREGATE".getBytes(),
>       "products",
>       "{}",
>       "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"
>   );
>   System.out.println(result);
> }
> ```

> **redis-rs**
>
> ```rust theme={"system"}
> fn main() -> redis::RedisResult<()> {
>     let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
>     let client = redis::Client::open(url)?;
>     let mut connection = client.get_connection()?;
>
>     let mut command = redis::cmd("SEARCH.AGGREGATE");
>     command.arg("products");
>     command.arg("{}");
>     command.arg("{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}");
>     let result: redis::Value = command.query(&mut connection)?;
>     println!("{result:?}");
>     Ok(())
> }
> ```

> **curl**
>
> ```bash theme={"system"}
> curl -X POST https://YOUR_ENDPOINT.upstash.io \
>   -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \
>   -d '["SEARCH.AGGREGATE", "products", "{}", "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"]'
> ```


## Related topics

- [$composite](/docs/redis/search/aggregation-operators/bucket-aggregations/composite.md)
- [$min](/docs/redis/search/aggregation-operators/metric-aggregations/min.md)
- [$terms](/docs/redis/search/aggregation-operators/bucket-aggregations/terms.md)
- [$count](/docs/redis/search/aggregation-operators/metric-aggregations/count.md)
- [Aggregations](/docs/redis/search/aggregations.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.