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

  1. Add an explicit alias: `count(*) AS row_count`
  2. For plain columns no alias is needed, but qualify expressions only when aliased
  3. 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

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


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