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 because

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Pass at least one column to update on conflict, e.g. ['updated_at'] or the mutable fields.
  2. If nothing should change on conflict, do not use DO UPDATE - use ON CONFLICT DO NOTHING (plain SQL) instead.
  3. 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

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


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/a2e0fd31b9c19639. Report an issue: GitHub.