> ## Documentation Index
> Fetch the complete documentation index at: https://upstash-redis-1-18.mintlify.site/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`](/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](/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](/redis/search/querying). The command accepts an index name or alias. See [Aggregations](/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.
</Note>

## Examples

<AccordionGroup>
  <Accordion title="Redis CLI" icon="terminal">
    ```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}}}]}}}'
    ```
  </Accordion>

  <Accordion title="@upstash/redis" icon="node-js" iconType="brands">
    ```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" } },
      },
    });
    ```
  </Accordion>

  <Accordion title="upstash_redis" icon="python" iconType="brands">
    ```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"}}},
    )
    ```
  </Accordion>

  <Accordion title="ioredis" icon="node-js" iconType="brands">
    ```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" } },
      },
    });
    ```
  </Accordion>

  <Accordion title="node-redis" icon="node-js" iconType="brands">
    ```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" } },
      },
    });
    ```
  </Accordion>

  <Accordion title="redis-py" icon="python" iconType="brands">
    ```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)
    ```
  </Accordion>

  <Accordion title="go-redis" icon="golang" iconType="brands">
    ```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)
    }
    ```
  </Accordion>

  <Accordion title="jedis" icon="java" iconType="brands">
    ```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);
    }
    ```
  </Accordion>

  <Accordion title="redis-rs" icon="rust" iconType="brands">
    ```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(())
    }
    ```
  </Accordion>

  <Accordion title="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\"}}}"]'
    ```
  </Accordion>
</AccordionGroup>
