go-redis/redis · error
FT.AGGREGATE: Collect must follow a GroupBy step
Error message
FT.AGGREGATE: Collect must follow a GroupBy step
What it means
Recorded (and later returned by Run) when AggregateBuilder.Collect is called but the most recent pipeline step is not a GROUPBY. Collect builds a REDUCE COLLECT clause that attaches to a GROUPBY step; like Reduce/ReduceAs, misordering defers the error to Run.
Source
Thrown at search_builders.go:328
}
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)
if n == 0 || b.options.Steps[n-1].GroupBy == nil {
b.setErr(fmt.Errorf("FT.AGGREGATE: Collect must follow a GroupBy step"))
return b
}
reducer, err := NewCollectReducer(o)
if err != nil {
b.setErr(err)
return b
}
g := b.options.Steps[n-1].GroupBy
g.Reduce = append(g.Reduce, reducer)
return b
}
// SortBy adds SORTBY <field> ASC|DESC. Consecutive SortBy calls (with no
// other step in between) are merged into a single SORTBY clause so fields
// act as tiebreakers. A SortBy call after a non-SortBy step starts a new
// SORTBY step.
//
// Note: this is a semantics change from earlier experimental versions ofView on GitHub (pinned to 36d97525cd)
Solutions
- Call b.GroupBy(...) immediately before b.Collect(o) so COLLECT attaches to a GROUPBY step.
- Remember COLLECT is a reducer, not a standalone step; pair it with GroupBy every time.
- Add a test that builds the pipeline and asserts Run does not return the ordering error.
Example fix
// before
b := c.NewAggregateBuilder(ctx, idx, q)
b.Collect(redis.FTAggregateCollect{Fields: []string{"@sku"}}) // no GroupBy
// after
b := c.NewAggregateBuilder(ctx, idx, q)
b.GroupBy("@order").Collect(redis.FTAggregateCollect{Fields: []string{"@sku"}}) Defensive patterns
Strategy: validation
Validate before calling
// COLLECT is a REDUCE clause; ensure a GroupBy precedes it.
if len(steps) == 0 || steps[len(steps)-1].GroupBy == nil {
return errors.New("Collect requires a preceding GroupBy")
} Try / catch
res, err := b.Run()
if err != nil && strings.Contains(err.Error(), "Collect must follow a GroupBy step") {
// add a GroupBy before Collect
} Prevention
- Always pair Collect with a GroupBy in the same fluent chain.
- Remember COLLECT requires Redis 8.8+ with unstable features; document this at the call site.
- Unit-test the builder to catch ordering and version-gating early.
When it happens
Trigger: Calling b.Collect(o) when the last step is not a GroupBy, or the builder has no steps. COLLECT also requires Redis 8.8+ with unstable features; the ordering check fires before any version validation.
Common situations: Reordered pipeline where Collect was placed before GroupBy; using Collect as if it were a top-level step rather than a REDUCE clause; refactoring that moved Collect above its GroupBy.
Related errors
- FT.AGGREGATE: Reduce must follow a GroupBy step
- FT.AGGREGATE: ReduceAs 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/656ed8042f7eb094.json.
Report an issue: GitHub.