{"id":"ae4d5973bf3c9ea4","repo":"go-redis/redis","slug":"ft-aggregate-reduce-must-follow-a-groupby-step","errorCode":null,"errorMessage":"FT.AGGREGATE: Reduce must follow a GroupBy step","messagePattern":"FT\\.AGGREGATE: Reduce must follow a GroupBy step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_builders.go","lineNumber":295,"sourceCode":"\t}\n\tb.options.Steps = append(b.options.Steps, FTAggregateStep{Apply: a})\n\treturn b\n}\n\n// GroupBy adds a new GROUPBY <fields...> step.\nfunc (b *AggregateBuilder) GroupBy(fields ...interface{}) *AggregateBuilder {\n\tb.options.Steps = append(b.options.Steps, FTAggregateStep{\n\t\tGroupBy: &FTAggregateGroupBy{Fields: fields},\n\t})\n\treturn b\n}\n\n// Reduce adds a REDUCE <fn> [<#args> <args...>] clause to the last step,\n// which must be a GROUPBY. If it is not, Run will return an error.\nfunc (b *AggregateBuilder) Reduce(fn SearchAggregator, args ...interface{}) *AggregateBuilder {\n\tn := len(b.options.Steps)\n\tif n == 0 || b.options.Steps[n-1].GroupBy == nil {\n\t\tb.setErr(fmt.Errorf(\"FT.AGGREGATE: Reduce must follow a GroupBy step\"))\n\t\treturn b\n\t}\n\tg := b.options.Steps[n-1].GroupBy\n\tg.Reduce = append(g.Reduce, FTAggregateReducer{Reducer: fn, Args: args})\n\treturn b\n}\n\n// ReduceAs does the same but also sets an alias: REDUCE <fn> … AS <alias>.\n// The last step must be a GROUPBY; otherwise Run will return an error.\nfunc (b *AggregateBuilder) ReduceAs(fn SearchAggregator, alias string, args ...interface{}) *AggregateBuilder {\n\tn := len(b.options.Steps)\n\tif n == 0 || b.options.Steps[n-1].GroupBy == nil {\n\t\tb.setErr(fmt.Errorf(\"FT.AGGREGATE: ReduceAs must follow a GroupBy step\"))\n\t\treturn b\n\t}\n\tg := b.options.Steps[n-1].GroupBy\n\tg.Reduce = append(g.Reduce, FTAggregateReducer{Reducer: fn, Args: args, As: alias})\n\treturn b","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_builders.go#L277-L313","documentation":"Recorded (and later returned by Run) when AggregateBuilder.Reduce is called but the most recent pipeline step is not a GROUPBY. REDUCE is a clause attached to a GROUPBY step; calling it without a preceding GroupBy is a misuse of the fluent API. The error is stored on the builder and surfaced from Run rather than panicking.","triggerScenarios":"Calling b.Reduce(fn, args...) on an AggregateBuilder that has no steps yet, or whose last step is a SortBy/Load/Filter rather than GroupBy. The error does not surface until Run() is called.","commonSituations":"Builder pipeline reordered during refactoring (Reduce accidentally placed before GroupBy); copy-paste of an aggregation snippet missing the GroupBy line; chaining Reduce after a Load step.","solutions":["Call b.GroupBy(fields...) immediately before b.Reduce(...) so the last step is the GROUPBY the reducer attaches to.","Inspect the builder's step order in tests; assert the step preceding Reduce is a GroupBy.","Run a small unit test that builds the pipeline and calls Run against a mock to catch ordering errors early."],"exampleFix":"// before\nb := c.NewAggregateBuilder(ctx, idx, q)\nb.Reduce(redis.SearchAggCount) // no GroupBy -> error at Run\nres, err := b.Run()\n\n// after\nb := c.NewAggregateBuilder(ctx, idx, q)\nb.GroupBy(\"@brand\").Reduce(redis.SearchAggCount)\nres, err := b.Run()","handlingStrategy":"validation","validationCode":"// Track step types in the builder and assert Reduce follows GroupBy.\nfunc requiresGroupBy(steps []FTAggregateStep) bool {\n    n := len(steps)\n    return n > 0 && steps[n-1].GroupBy != nil\n}","typeGuard":null,"tryCatchPattern":"res, err := b.Run()\nif err != nil && strings.Contains(err.Error(), \"Reduce must follow a GroupBy step\") {\n    // fix the builder pipeline order; this is a programming error\n}","preventionTips":["Chain GroupBy().Reduce(...) in a single fluent expression to make ordering obvious.","Add a unit test that builds each pipeline and asserts Run does not return an ordering error.","Code-review builder chains specifically for step ordering."],"tags":["ftaggregate","search","builder","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}