vitessio/vitess · error
expression needs an alias: %v
Error message
expression needs an alias: %v
What it means
Non-trivial select expressions (anything that is not a bare column reference) must carry an explicit alias so the builder can name the resulting field. An unaliased expression like `count(*)` or `upper(name)` fails with this error because the output column name would be ambiguous.
Source
Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:440
return err
}
tpb.colExprs = append(tpb.colExprs, cexpr)
}
return nil
}
func (tpb *tablePlanBuilder) analyzeExpr(selExpr sqlparser.SelectExpr) (*colExpr, error) {
aliased, ok := selExpr.(*sqlparser.AliasedExpr)
if !ok {
return nil, fmt.Errorf("invalid expression: %v", sqlparser.String(selExpr))
}
as := aliased.As
if as.IsEmpty() {
// Require all non-trivial expressions to have an alias.
if colAs, ok := aliased.Expr.(*sqlparser.ColName); ok && colAs.Qualifier.IsEmpty() {
as = colAs.Name
} else {
return nil, fmt.Errorf("expression needs an alias: %v", sqlparser.String(aliased))
}
}
cexpr := &colExpr{
colName: as,
references: make(map[string]bool),
}
if expr, ok := aliased.Expr.(*sqlparser.ConvertUsingExpr); ok {
// Here we find the actual column name in the convert, in case
// this is a column rename and the AS is the new column.
// For example, in convert(c1 using utf8mb4) as c2, we want to find
// c1, because c1 exists in the current table whereas c2 is the renamed column
// in the desired table.
var colName sqlparser.IdentifierCI
err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
switch node := node.(type) {
case *sqlparser.ColName:
if !node.Qualifier.IsEmpty() {
return false, fmt.Errorf("unsupported qualifier for column: %v", sqlparser.String(node))View on GitHub (pinned to 01a25a7d17)
Solutions
- Add an explicit alias: `count(*) AS row_count`
- For plain columns no alias is needed, but qualify expressions only when aliased
- Re-run the workflow after fixing the select list
Example fix
// before SELECT count(*) FROM t // after SELECT count(*) AS row_count FROM t
Defensive patterns
Strategy: validation
Validate before calling
for _, e := range sel.SelectExprs {
ae, ok := e.(*sqlparser.AliasedExpr)
if !ok { continue }
if ae.As.IsEmpty() {
if _, isCol := ae.Expr.(*sqlparser.ColName); !isCol {
return fmt.Errorf("expression %v requires AS alias", sqlparser.String(ae))
}
}
} Type guard
func hasAlias(ae *sqlparser.AliasedExpr) bool {
if !ae.As.IsEmpty() { return true }
col, ok := ae.Expr.(*sqlparser.ColName)
return ok && col.Qualifier.IsEmpty()
} Try / catch
if err != nil && strings.Contains(err.Error(), "needs an alias") {
return fmt.Errorf("add AS aliases to all function expressions in filter select list: %w", err)
} Prevention
- Always alias non-column expressions in filter rules
- Lint filter SQL for bare function calls
- Document required AS for aggregate/function expressions
When it happens
Trigger: A filter select list includes an expression such as `SELECT count(*) FROM t` or `SELECT concat(a,b) FROM t` without `AS name`.
Common situations: Copy-phase rules computing values without aliases; hand-written filter rules forgetting the AS clause for functions.
Related errors
- invalid expression: %v
- unsupported from expression (%T)
- unsupported from source (%T)
- unsupported qualifier for column: %v
- failed to find column name for convert using expression: %v,
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/3e43caead2d57a39.
Report an issue: GitHub.