{"id":"fbfda4deecabf1fa","repo":"go-redis/redis","slug":"ft-aggregate-reduceas-must-follow-a-groupby-step","errorCode":null,"errorMessage":"FT.AGGREGATE: ReduceAs must follow a GroupBy step","messagePattern":"FT\\.AGGREGATE: ReduceAs must follow a GroupBy step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_builders.go","lineNumber":308,"sourceCode":"// 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\n}\n\n// Collect adds a REDUCE COLLECT clause to the last step, which must be a\n// GROUPBY. The COLLECT options (FIELDS/DISTINCT/SORTBY/LIMIT/AS) are rendered\n// and the argument count is computed automatically; field and sort names are\n// normalized to a single \"@\" prefix. Set FTAggregateCollect.As to alias the\n// output column.\n//\n// If the last step is not a GROUPBY, or the options are invalid (no FIELDS\n// selector), Run returns the recorded error without issuing the command.\n// COLLECT requires Redis 8.8+ with unstable features enabled.\nfunc (b *AggregateBuilder) Collect(o FTAggregateCollect) *AggregateBuilder {\n\tn := len(b.options.Steps)","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_builders.go#L290-L326","documentation":"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.","triggerScenarios":"Calling b.ReduceAs(fn, alias, args...) when the last step is not a GroupBy (empty builder, or last step is SortBy/Load/etc.).","commonSituations":"Same as Reduce: reordered pipeline, missing GroupBy call, or chained ReduceAs after a non-GROUPBY step during refactoring.","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."],"exampleFix":"// before\nb.SortBy(\"@price\", true)\nb.ReduceAs(redis.SearchAggSum, \"total\", \"@price\") // last step is SortBy\n\n// after\nb.GroupBy(\"@brand\").ReduceAs(redis.SearchAggSum, \"total\", \"@price\")\nb.SortBy(\"@price\", true)","handlingStrategy":"validation","validationCode":"// Same helper as Reduce: last step must be GroupBy before ReduceAs.\nif len(steps) == 0 || steps[len(steps)-1].GroupBy == nil {\n    return errors.New(\"ReduceAs requires a preceding GroupBy\")\n}","typeGuard":null,"tryCatchPattern":"res, err := b.Run()\nif err != nil && strings.Contains(err.Error(), \"ReduceAs must follow a GroupBy step\") {\n    // programming error: reorder builder calls\n}","preventionTips":["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."],"tags":["ftaggregate","search","builder","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}