vitessio/vitess · error

VT12001

VT12001

Error message

VT12001: unsupported: in scatter query: complex aggregate expression

What it means

VT12001 is raised when AggregationExpressions encounters an aggregate expression that is neither a simple aggregate nor permitted as a complex expression, in a scatter query. Scatter queries aggregate results across shards, so each output column must be a directly pushable aggregate; arbitrary complex expressions over aggregates cannot be computed correctly from partial per-shard results.

Source

Thrown at go/vt/vtgate/planbuilder/operators/queryprojection.go:442

		if !ctx.ContainsAggr(selectExpr.Col) {
			getExpr, err := selectExpr.GetExpr()
			if err != nil {
				panic(err)
			}

			if ctx.ContainsWindowFunc(selectExpr.Col) {
				sqlparser.CopyOnRewrite(aliasedExpr.Expr, qp.extractAggr(ctx, aliasedExpr, addAggr, makeComplex), nil, nil)
				continue
			}

			if !qp.isExprInGroupByExprs(ctx, getExpr) {
				aggr := createNonGroupingAggr(aliasedExpr)
				out = append(out, aggr)
			}
			continue
		}
		if !ctx.IsAggr(aliasedExpr.Expr) && !allowComplexExpression {
			panic(vterrors.VT12001("in scatter query: complex aggregate expression"))
		}

		sqlparser.CopyOnRewrite(aliasedExpr.Expr, qp.extractAggr(ctx, aliasedExpr, addAggr, makeComplex), nil, nil)
	}
	return
}

func (qp *QueryProjection) extractAggr(
	ctx *plancontext.PlanningContext,
	aliasedExpr *sqlparser.AliasedExpr,
	addAggr func(a Aggr),
	makeComplex func(),
) func(node sqlparser.SQLNode, parent sqlparser.SQLNode) bool {
	return func(node, parent sqlparser.SQLNode) bool {
		ex, isExpr := node.(sqlparser.Expr)
		if !isExpr {
			return true
		}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Rewrite the query so each aggregate is a simple top-level function, e.g. `SELECT SUM(a), SUM(b) FROM t` and compute `SUM(a)-SUM(b)` in the application.
  2. Add a predicate on the sharding key (e.g. `WHERE sharding_key = ?`) so the query routes to a single shard instead of scattering.
  3. Restructure the VSchema/sharding or use an unsharded keyspace if the aggregate pattern is common.

Example fix

// before
SELECT SUM(price) / COUNT(*) AS avg_price FROM orders;
// after
SELECT SUM(price) AS total, COUNT(*) AS cnt FROM orders; // divide client-side
Defensive patterns

Strategy: try-catch

Validate before calling

// Reject complex arithmetic over aggregates for scatter routes before sending
if isScatterRoute(query) && containsNestedAggregateExpr(query) {
    return errors.New("rewrite query: scatter queries only support simple top-level aggregates")
}

Try / catch

if strings.Contains(err.Error(), "VT12001") && strings.Contains(err.Error(), "scatter") {
    // fall back to splitting aggregates client-side or route with sharding key
}

Prevention

When it happens

Trigger: createProjectionWithAggr calls AggregationExpressions with allowComplexExpression=false for a scatter-route query whose SELECT contains a complex expression mixing aggregates with other expressions (e.g. `SUM(x)/COUNT(y)` composed with extra terms the planner can't push down).

Common situations: Aggregates over sharded tables without a usable sharding-key predicate (forcing scatter), combined with complex expressions like arithmetic on aggregates or nested functions, e.g. `SELECT SUM(a) - SUM(b) FROM t` on a scatter.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/f5a98864c9e3a38c. Report an issue: GitHub.