vitessio/vitess · error
VT09015
VT09015
Error message
VT09015
What it means
VT09015: unsupported subquery in this projection context. During projection building (after a route/aggregation has been collapsed), the planner walks each projected column and panics with VT09015 if it finds a *sqlparser.Subquery, because subqueries cannot be evaluated inside this operator's projection on the chosen route.
Source
Thrown at go/vt/vtgate/planbuilder/operators/horizon_expanding.go:366
proj.addUnexploredExpr(org, expr)
} else {
proj.addSubqueryExpr(ctx, org, newExpr, subqs...)
}
}
proj.Source = sqc.getRootOperator(src, nil)
return proj
}
func newStarProjection(src Operator, qp *QueryProjection) *Projection {
cols := make([]sqlparser.SelectExpr, 0, len(qp.SelectExprs))
for _, expr := range qp.SelectExprs {
_ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
_, isSubQ := node.(*sqlparser.Subquery)
if !isSubQ {
return true, nil
}
panic(vterrors.VT09015())
}, expr.Col)
cols = append(cols, expr.Col)
}
return &Projection{
unaryOperator: newUnaryOp(src),
Columns: StarProjections(cols),
}
}
View on GitHub (pinned to 01a25a7d17)
Solutions
- Rewrite the subquery as a JOIN against a derived table: `SELECT d.m FROM t JOIN (SELECT MAX(x) AS m FROM t2) d`
- Move the subquery result into a derived table and reference its column in the projection
- Split into two queries executed by the application
- Check Vitess release notes — subquery support expands over time; upgrade may help
Example fix
// before SELECT a, (SELECT MAX(x) FROM t2) AS m FROM t GROUP BY a; // after SELECT t.a, d.m FROM t JOIN (SELECT MAX(x) AS m FROM t2) AS d ON TRUE GROUP BY t.a, d.m;
Defensive patterns
Strategy: validation
Validate before calling
// Reject scalar subqueries in SELECT list before sending to Vitess
func selectContainsSubquery(stmt sqlparser.Statement) bool {
found := false
_ = sqlparser.Walk(func(n sqlparser.SQLNode) (bool, error) {
if _, ok := n.(*sqlparser.Subquery); ok { found = true; return false, nil }
return true, nil
}, stmt)
return found
} Type guard
_, isSubquery := node.(*sqlparser.Subquery) // isSubquery true => unsupported here
Try / catch
err := exec(q)
if err != nil && strings.Contains(err.Error(), "VT09015") {
// rewrite query to use JOIN against derived table instead
} Prevention
- Rewrite scalar subqueries in SELECT lists as JOINs with derived tables
- Keep subqueries in WHERE/FROM clauses where Vitess support is broader
- Consult Vitess subquery support docs for your version before adopting patterns
- Add pre-deployment SQL linting to flag SELECT-list subqueries
When it happens
Trigger: A SELECT list (of a collapsed/merged query, e.g. inside an aggregation or post-processing projection) contains a scalar subquery expression, e.g. `SELECT (SELECT MAX(x) FROM t2) FROM t GROUP BY a`.
Common situations: Queries ported from application code that relies on scalar subqueries in the SELECT list combined with GROUP BY or other constructs that force them into the internal projection operator.
Related errors
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/cbb8944cd130825a.
Report an issue: GitHub.