go-redis/redis · error
FT.AGGREGATE: ReduceAs must follow a GroupBy step
Error message
FT.AGGREGATE: ReduceAs must follow a GroupBy step
What it means
Recorded (and later returned by Run) when AggregateBuilder.ReduceAs is called but the most recent pipeline step is not a GROUPBY. ReduceAs is the aliased form of Reduce (REDUCE fn AS alias); the same ordering requirement applies and the error is deferred to 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 36d97525cd)
Solutions
- Ensure b.GroupBy(...) is the immediately preceding step before b.ReduceAs(...).
- Add a builder-order assertion in tests to catch misordered ReduceAs calls.
- Use the fluent chain in a single expression to make ordering visually obvious.
Example fix
// before
b.SortBy("@price", true)
b.ReduceAs(redis.SearchAggSum, "total", "@price") // last step is SortBy
// after
b.GroupBy("@brand").ReduceAs(redis.SearchAggSum, "total", "@price")
b.SortBy("@price", true) Defensive patterns
Strategy: validation
Validate before calling
// Same helper as Reduce: last step must be GroupBy before ReduceAs.
if len(steps) == 0 || steps[len(steps)-1].GroupBy == nil {
return errors.New("ReduceAs requires a preceding GroupBy")
} Try / catch
res, err := b.Run()
if err != nil && strings.Contains(err.Error(), "ReduceAs must follow a GroupBy step") {
// programming error: reorder builder calls
} Prevention
- Keep ReduceAs directly after its GroupBy in the fluent chain.
- Test builder pipelines against a mock client to surface deferred errors at test time.
- Treat Run returning an ordering error as a build-time bug, not a runtime condition.
When it happens
Trigger: Calling b.ReduceAs(fn, alias, args...) when the last step is not a GroupBy (empty builder, or last step is SortBy/Load/etc.).
Common situations: Same as Reduce: reordered pipeline, missing GroupBy call, or chained ReduceAs after a non-GROUPBY step during refactoring.
Related errors
- FT.AGGREGATE: Reduce must follow a GroupBy step
- FT.AGGREGATE: Collect must follow a GroupBy step
- FT.AGGREGATE: SortByMax must follow a SortBy step
- redis: FT.AGGREGATE COLLECT: empty field name in Fields
- redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empt
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/fbfda4deecabf1fa.json.
Report an issue: GitHub.