vitessio/vitess · error

primary key column %v is not allowed to reference an aggrega

Error message

primary key column %v is not allowed to reference an aggregate expression

What it means

analyzePK requires each primary key column to map to a plain column expression (opExpr). If the PK column resolves to an aggregate expression in the select filter (e.g. SUM(x) or COUNT(*)), the copied rows could not be uniquely identified, so plan construction is aborted.

Source

Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:613

	for _, col := range cols {
		if !col.IsPK {
			continue
		}
		if col.IsGenerated {
			// It's possible that a GENERATED column is part of the PRIMARY KEY. That's valid.
			// But then, we also know that we don't actually SELECT a GENERATED column, we just skip
			// it silently and let it re-materialize by MySQL itself on the target.
			continue
		}
		cexpr := tpb.findCol(sqlparser.NewIdentifierCI(col.Name))
		if cexpr == nil {
			// TODO(shlomi): at some point in the futue we want to make this check stricter.
			// We could be reading a generated column c1 which in turn selects some other column c2.
			// We will want t oensure that `c2` is found in select list...
			return fmt.Errorf("primary key column %v not found in table's select filter or the TableMap event within the GTID", col)
		}
		if cexpr.operation != opExpr {
			return fmt.Errorf("primary key column %v is not allowed to reference an aggregate expression", col)
		}
		cexpr.isPK = true
		cexpr.dataType = col.DataType
		cexpr.columnType = col.ColumnType
		tpb.pkCols = append(tpb.pkCols, cexpr)
	}
	return nil
}

// analyzeExtraSourcePkCols builds tpb.extraSourcePkCols.
// VReplication allows source and target tables to use different unique keys. Normally, both will
// use same PRIMARY KEY. Other times, same other UNIQUE KEY. But it's possible that source and target
// unique keys will only have partial (or empty) shared list of columns.
// To be able to generate UPDATE/DELETE queries correctly, we need to know the identities of the
// source unique key columns, that are not already part of the target unique key columns. We call
// those columns "extra source pk columns". We will use them in the `WHERE` clause.
func (tpb *tablePlanBuilder) analyzeExtraSourcePkCols(colInfos []*ColumnInfo, sourceKeyTargetColumnNames []string) error {
	sourceKeyTargetColumnNamesMap := map[string]bool{}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Rewrite the filter select so the PK column is selected as a plain column, not via an aggregate or computed expression
  2. Use a non-aggregate alias distinct from the PK column name for any aggregate you need
  3. Re-run the workflow after correcting the select expression

Example fix

// before
"select max(id) as id, name from t"
// after
"select id, max(created_at) as latest, name from t"
Defensive patterns

Strategy: validation

Validate before calling

// Ensure PK columns are selected plainly, not via aggregates/expressions
-- bad: SELECT max(id) AS id FROM t
-- good: SELECT id FROM t

Prevention

When it happens

Trigger: A Filter select expression that maps a table's PK column name to an aggregate expression, e.g. 'select max(id) as id from t' where id is the PK; buildFromFields/buildTablePlan call analyzePK and hit cexpr.operation != opExpr.

Common situations: Hand-written filter select expressions with aggregates or computed aliases that shadow PK column names; accidental use of GROUP BY-style selects in MoveTables filters.

Related errors


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