phalcon/cphalcon · error · ConflictUpdateColumnRequired
ON CONFLICT DO UPDATE requires at least one update column
Error message
ON CONFLICT DO UPDATE requires at least one update column
What it means
Dialect::onConflictUpdate() requires a non-empty updateColumns list: those columns become the `DO UPDATE SET col = excluded.col` assignments. An empty list means the upsert would do nothing on conflict, so the method throws ConflictUpdateColumnRequired after the conflict-target check.
Source
Thrown at phalcon/Db/Dialect.zep:553
/**
* Appends an `ON CONFLICT (col, ...) DO UPDATE SET col = excluded.col`
* upsert clause to the supplied INSERT statement. The syntax is the
* SQL standard form recognized by PostgreSQL (9.5+) and SQLite (3.24+).
* MySQL overrides this method to throw because its `ON DUPLICATE KEY
* UPDATE` has a different shape (deferred to parser item #23).
*/
public function onConflictUpdate( string sqlQuery, array conflictColumns, array updateColumns) -> string
{
var col;
array assignments;
if unlikely empty conflictColumns {
throw new ConflictTargetColumnRequired();
}
if unlikely empty updateColumns {
throw new ConflictUpdateColumnRequired();
}
let assignments = [];
for col in updateColumns {
let assignments[] = this->escape((string) col)
. " = excluded." . this->escape((string) col);
}
return sqlQuery
. " ON CONFLICT (" . this->getColumnList(conflictColumns) . ")"
. " DO UPDATE SET " . implode(", ", assignments);
}
/**
* Returns a SQL statement extended with a `RETURNING` clause so the
* INSERT/UPDATE/DELETE returns rows. Supported by PostgreSQL and
* SQLite 3.35+. Pass `["*"]` for `RETURNING *`, or a list of column
* names. The base implementation throws - MySQL inherits it becauseView on GitHub (pinned to b7419de9cd)
Solutions
- Pass at least one column to update on conflict, e.g. ['updated_at'] or the mutable fields.
- If nothing should change on conflict, do not use DO UPDATE - use ON CONFLICT DO NOTHING (plain SQL) instead.
- Validate count($updateColumns) > 0 in your upsert wrapper before calling the dialect.
Example fix
// before
$update = array_diff($allColumns, ['sku']); // [] when table has only the key column
$sql = $dialect->onConflictUpdate($insertSql, ['sku'], $update); // throws ConflictUpdateColumnRequired
// after
$update = array_values(array_diff($allColumns, ['sku']));
if ($update === []) {
$sql = $insertSql . ' ON CONFLICT (sku) DO NOTHING';
} else {
$sql = $dialect->onConflictUpdate($insertSql, ['sku'], $update);
} Defensive patterns
Strategy: validation
Validate before calling
if (count($updateColumns) === 0) {
throw new InvalidArgumentException('ON CONFLICT DO UPDATE requires update columns');
}
$sql = $dialect->onConflictUpdate($insertSql, $conflictColumns, $updateColumns); Type guard
function hasUpdateColumns(array $updateColumns): bool
{
return $updateColumns !== []
&& array_filter($updateColumns, 'is_string') !== [];
} Prevention
- If nothing should change on conflict, emit ON CONFLICT DO NOTHING instead of DO UPDATE.
- When deriving update columns as insert-columns minus key columns, handle the empty case explicitly.
- Validate both upsert arrays in a wrapper before calling the dialect.
When it happens
Trigger: Calling $dialect->onConflictUpdate($insertSql, ['id'], []) - e.g. computing update columns as array_diff($columns, $conflictColumns) when every column is part of the key; passing column names in the first array only.
Common situations: Upsert helpers deriving update columns from insert columns minus key columns, where the table has no non-key columns; config-driven upsert specs with an empty update list; argument-order mixups.
Related errors
- ON CONFLICT requires at least one conflict-target column
- Invalid SQL expression
- ON CONFLICT upserts are not supported by MySQL; use INSERT .
- Headers have already been sent; cannot emit the response.
- No route matched the request.
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/a2e0fd31b9c19639.
Report an issue: GitHub.