{"record":{"id":"08e2f045d33e12de","repo":"redis/go-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/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/search_builders.go#L290-L326","documentation":"Identical to error 207 but raised by ReduceAs(), which adds an aliased REDUCE clause. The last builder step must be a GROUPBY; otherwise the error is recorded and returned by Run().","triggerScenarios":"Calling AggregateBuilder.ReduceAs(fn, alias, args...) when the builder has no steps or the last step is not a GroupBy, at search_builders.go:308.","commonSituations":"Chaining ReduceAs after Cursor/SortBy/Apply; forgetting that Filter and SortBy do not create a GroupBy step; building queries conditionally where the GroupBy call is skipped by a branch.","solutions":["Ensure GroupBy(...) is called immediately before ReduceAs(...)","Check conditional builder code so the GroupBy branch always runs when ReduceAs is used","Inspect the stored builder error (Run returns it) to confirm step ordering"],"exampleFix":"// before\nb := c.FTAggregate(ctx, \"idx\").Query(\"*\").SortBy(\"@ts\", false).ReduceAs(redis.SearchCount, \"cnt\")\n// after\nb := c.FTAggregate(ctx, \"idx\").Query(\"*\").GroupBy(\"@cat\").ReduceAs(redis.SearchCount, \"cnt\")","handlingStrategy":"validation","validationCode":"func addReduceAs(b *redis.AggregateBuilder, groupBy string, fn redis.SearchAggregator, alias string, args ...interface{}) *redis.AggregateBuilder {\n    return b.GroupBy(groupBy).ReduceAs(fn, alias, args...)\n}","typeGuard":null,"tryCatchPattern":"cmd := b.Run(ctx)\nif err := cmd.Err(); err != nil {\n    if strings.Contains(err.Error(), \"ReduceAs must follow a GroupBy\") {\n        return fmt.Errorf(\"bad FT.AGGREGATE query: %w\", err)\n    }\n    return err\n}","preventionTips":["Keep GroupBy immediately preceding ReduceAs in the chain","Avoid inserting SortBy/Filter between GroupBy and ReduceAs","Test aggregate builders with Run() in unit tests"],"tags":["go","redisearch","aggregate","query-builder"],"backgroundTag":"invalid-aggregate-query-order","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}