{"record":{"id":"64ac6eb9ecf82033","repo":"vitessio/vitess","slug":"primary-key-column-v-is-not-allowed-to-reference","errorCode":null,"errorMessage":"primary key column %v is not allowed to reference an aggregate expression","messagePattern":"primary key column (.+?) is not allowed to reference an aggregate expression","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go","lineNumber":613,"sourceCode":"\tfor _, col := range cols {\n\t\tif !col.IsPK {\n\t\t\tcontinue\n\t\t}\n\t\tif col.IsGenerated {\n\t\t\t// It's possible that a GENERATED column is part of the PRIMARY KEY. That's valid.\n\t\t\t// But then, we also know that we don't actually SELECT a GENERATED column, we just skip\n\t\t\t// it silently and let it re-materialize by MySQL itself on the target.\n\t\t\tcontinue\n\t\t}\n\t\tcexpr := tpb.findCol(sqlparser.NewIdentifierCI(col.Name))\n\t\tif cexpr == nil {\n\t\t\t// TODO(shlomi): at some point in the futue we want to make this check stricter.\n\t\t\t// We could be reading a generated column c1 which in turn selects some other column c2.\n\t\t\t// We will want t oensure that `c2` is found in select list...\n\t\t\treturn fmt.Errorf(\"primary key column %v not found in table's select filter or the TableMap event within the GTID\", col)\n\t\t}\n\t\tif cexpr.operation != opExpr {\n\t\t\treturn fmt.Errorf(\"primary key column %v is not allowed to reference an aggregate expression\", col)\n\t\t}\n\t\tcexpr.isPK = true\n\t\tcexpr.dataType = col.DataType\n\t\tcexpr.columnType = col.ColumnType\n\t\ttpb.pkCols = append(tpb.pkCols, cexpr)\n\t}\n\treturn nil\n}\n\n// analyzeExtraSourcePkCols builds tpb.extraSourcePkCols.\n// VReplication allows source and target tables to use different unique keys. Normally, both will\n// use same PRIMARY KEY. Other times, same other UNIQUE KEY. But it's possible that source and target\n// unique keys will only have partial (or empty) shared list of columns.\n// To be able to generate UPDATE/DELETE queries correctly, we need to know the identities of the\n// source unique key columns, that are not already part of the target unique key columns. We call\n// those columns \"extra source pk columns\". We will use them in the `WHERE` clause.\nfunc (tpb *tablePlanBuilder) analyzeExtraSourcePkCols(colInfos []*ColumnInfo, sourceKeyTargetColumnNames []string) error {\n\tsourceKeyTargetColumnNamesMap := map[string]bool{}","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go#L595-L631","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rewrite the filter select so the PK column is selected as a plain column, not via an aggregate or computed expression","Use a non-aggregate alias distinct from the PK column name for any aggregate you need","Re-run the workflow after correcting the select expression"],"exampleFix":"// before\n\"select max(id) as id, name from t\"\n// after\n\"select id, max(created_at) as latest, name from t\"","handlingStrategy":"validation","validationCode":"// Ensure PK columns are selected plainly, not via aggregates/expressions\n-- bad: SELECT max(id) AS id FROM t\n-- good: SELECT id FROM t","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never alias an aggregate or computed expression to a PK column name","Keep filter selects to plain column lists for copied tables","Lint workflow filters before applying them"],"tags":["vreplication","plan-builder","primary-key","aggregate"],"backgroundTag":"pk-column-references-aggregate","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}