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

  1. Ensure b.GroupBy(...) is the immediately preceding step before b.ReduceAs(...).
  2. Add a builder-order assertion in tests to catch misordered ReduceAs calls.
  3. 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

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


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/fbfda4deecabf1fa.json. Report an issue: GitHub.