redis/go-redis · error
FT.AGGREGATE: ReduceAs must follow a GroupBy step
Error message
FT.AGGREGATE: ReduceAs must follow a GroupBy step
What it means
Identical to error 207 but raised by ReduceAs(), which adds an aliased REDUCE clause. The last builder step must be a GROUPBY; otherwise the error is recorded and returned by Run().
Source
Thrown at search_builders.go:308
// Reduce adds a REDUCE <fn> [<#args> <args...>] clause to the last step,
// which must be a GROUPBY. If it is not, Run will return an error.
func (b *AggregateBuilder) Reduce(fn SearchAggregator, args ...interface{}) *AggregateBuilder {
n := len(b.options.Steps)
if n == 0 || b.options.Steps[n-1].GroupBy == nil {
b.setErr(fmt.Errorf("FT.AGGREGATE: Reduce must follow a GroupBy step"))
return b
}
g := b.options.Steps[n-1].GroupBy
g.Reduce = append(g.Reduce, FTAggregateReducer{Reducer: fn, Args: args})
return b
}
// ReduceAs does the same but also sets an alias: REDUCE <fn> … AS <alias>.
// The last step must be a GROUPBY; otherwise Run will return an error.
func (b *AggregateBuilder) ReduceAs(fn SearchAggregator, alias string, args ...interface{}) *AggregateBuilder {
n := len(b.options.Steps)
if n == 0 || b.options.Steps[n-1].GroupBy == nil {
b.setErr(fmt.Errorf("FT.AGGREGATE: ReduceAs must follow a GroupBy step"))
return b
}
g := b.options.Steps[n-1].GroupBy
g.Reduce = append(g.Reduce, FTAggregateReducer{Reducer: fn, Args: args, As: alias})
return b
}
// Collect adds a REDUCE COLLECT clause to the last step, which must be a
// GROUPBY. The COLLECT options (FIELDS/DISTINCT/SORTBY/LIMIT/AS) are rendered
// and the argument count is computed automatically; field and sort names are
// normalized to a single "@" prefix. Set FTAggregateCollect.As to alias the
// output column.
//
// If the last step is not a GROUPBY, or the options are invalid (no FIELDS
// selector), Run returns the recorded error without issuing the command.
// COLLECT requires Redis 8.8+ with unstable features enabled.
func (b *AggregateBuilder) Collect(o FTAggregateCollect) *AggregateBuilder {
n := len(b.options.Steps)View on GitHub (pinned to c5cad058c7)
Solutions
- Ensure GroupBy(...) is called immediately before ReduceAs(...)
- Check conditional builder code so the GroupBy branch always runs when ReduceAs is used
- Inspect the stored builder error (Run returns it) to confirm step ordering
Example fix
// before
b := c.FTAggregate(ctx, "idx").Query("*").SortBy("@ts", false).ReduceAs(redis.SearchCount, "cnt")
// after
b := c.FTAggregate(ctx, "idx").Query("*").GroupBy("@cat").ReduceAs(redis.SearchCount, "cnt") Defensive patterns
Strategy: validation
Validate before calling
func addReduceAs(b *redis.AggregateBuilder, groupBy string, fn redis.SearchAggregator, alias string, args ...interface{}) *redis.AggregateBuilder {
return b.GroupBy(groupBy).ReduceAs(fn, alias, args...)
} Try / catch
cmd := b.Run(ctx)
if err := cmd.Err(); err != nil {
if strings.Contains(err.Error(), "ReduceAs must follow a GroupBy") {
return fmt.Errorf("bad FT.AGGREGATE query: %w", err)
}
return err
} Prevention
- Keep GroupBy immediately preceding ReduceAs in the chain
- Avoid inserting SortBy/Filter between GroupBy and ReduceAs
- Test aggregate builders with Run() in unit tests
When it happens
Trigger: Calling AggregateBuilder.ReduceAs(fn, alias, args...) when the builder has no steps or the last step is not a GroupBy, at search_builders.go:308.
Common situations: Chaining ReduceAs after Cursor/SortBy/Apply; forgetting that Filter and SortBy do not create a GroupBy step; building queries conditionally where the GroupBy call is skipped by a branch.
Related errors
- FT.AGGREGATE: Reduce must follow a GroupBy step
- failed to create operation duration histogram: %w
- failed to create connection count metric: %w
- failed to create connection create time histogram: %w
- failed to create connection relaxed timeout metric: %w
AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01).
Data as JSON: /api/errors/08e2f045d33e12de.
Report an issue: GitHub.