sqlalchemy/sqlalchemy · error · ValueError
set parameter dictionary must not be empty
Error message
set parameter dictionary must not be empty
What it means
Raised by OnConflictDoUpdate.__init__ when the set_ parameter is a dict but is empty. The SQLite ON CONFLICT DO UPDATE clause requires at least one column to update, so an empty assignment set is rejected with ValueError at statement construction. This is the dedicated, more specific error for the empty-dict case before the general type-validation error in the elif/else branch.
Source
Thrown at lib/sqlalchemy/dialects/sqlite/dml.py:260
("update_values_to_set", InternalTraversal.dp_dml_values),
("update_whereclause", InternalTraversal.dp_clauseelement),
]
def __init__(
self,
index_elements: _OnConflictIndexElementsT = None,
index_where: _OnConflictIndexWhereT = None,
set_: _OnConflictSetT = None,
where: _OnConflictWhereT = None,
):
super().__init__(
index_elements=index_elements,
index_where=index_where,
)
if isinstance(set_, dict):
if not set_:
raise ValueError("set parameter dictionary must not be empty")
elif isinstance(set_, ColumnCollection):
set_ = dict(set_)
else:
raise ValueError(
"set parameter must be a non-empty dictionary "
"or a ColumnCollection such as the `.c.` collection "
"of a Table object"
)
self.update_values_to_set = {
coercions.expect(roles.DMLColumnRole, k): coercions.expect(
roles.ExpressionElementRole, v, type_=NULLTYPE, is_crud=True
)
for k, v in set_.items()
}
self.update_whereclause = (
coercions.expect(roles.WhereHavingRole, where)
if where is not None
else NoneView on GitHub (pinned to d9b44cb731)
Solutions
- Ensure the set_ dict has at least one entry; if it may be empty, skip emitting on_conflict_do_update entirely (use on_conflict_do_nothing or no conflict clause).
- Guard before building the statement: `if updates: stmt = stmt.on_conflict_do_update(set_=updates) else: stmt = stmt.on_conflict_do_nothing()`.
- Default to a meaningful column such as the updated_at timestamp when no other fields change.
- Validate upstream input so empty payloads are rejected before reaching the DB layer.
Example fix
// before
stmt = sqlite_insert(User).values(id=1, name="x").on_conflict_do_update(
index_elements=[User.id],
set_={}, # raises ValueError
)
// after
updates = {"name": "x"} # computed upstream
conflict_clause = (
sqlite_insert(User).values(id=1, name="x")
.on_conflict_do_update(index_elements=[User.id], set_=updates)
if updates
else sqlite_insert(User).values(id=1, name="x").on_conflict_do_nothing(index_elements=[User.id])
) Defensive patterns
Strategy: validation
Validate before calling
# before building insert(t).on_conflict_do_update(set_=set_):
if isinstance(set_, dict) and not set_:
raise ValueError("on_conflict_do_update(set_=...) dict must not be empty")
# optionally rebuild from non-None values, or skip the upsert if nothing remains:
set_ = {k: v for k, v in set_.items() if v is not None}
if not set_:
stmt = insert(t).on_conflict_do_nothing() # nothing to update Type guard
from sqlalchemy.sql.base import ColumnCollection
def is_valid_on_conflict_set(s) -> bool:
return (isinstance(s, ColumnCollection)
or (isinstance(s, dict) and len(s) > 0)) Try / catch
try:
stmt = insert(t).on_conflict_do_update(set_=set_)
except ValueError as e:
if "must not be empty" in str(e):
set_ = {k: v for k, v in set_.items() if v is not None}
stmt = (insert(t).on_conflict_do_update(set_=set_)
if set_ else insert(t).on_conflict_do_nothing()) Prevention
- Ensure the dict passed to on_conflict_do_update(set_=...) contains at least one key.
- Filter out None/empty values before building set_; if it ends up empty, use on_conflict_do_nothing() instead.
- For whole-row updates pass the table's .c ColumnCollection rather than a dict.
When it happens
Trigger: Calling sqlite.insert(table).on_conflict_do_update(set_={}) or building the set_ dict dynamically such that it ends up empty (e.g. all keys filtered out, a comprehension that yields nothing, or a dict built from an empty config).
Common situations: Dynamically constructing the SET clause from a request payload or diff that contains no fields. Conditional update builders that filter to zero columns. Copy-paste templates left with a placeholder empty dict. Logic that computes changed columns and finds none.
Related errors
- set parameter must be a non-empty dictionary or a ColumnColl
- update parameter dictionary must not be empty
- update parameter must be a non-empty dictionary or a ColumnC
- 'constraint' and 'index_elements' are mutually exclusive
- Either constraint or index_elements, but not both, must be s
AI-assisted analysis of sqlalchemy/sqlalchemy@d9b44cb731 (2026-08-01).
Data as JSON: /data/errors/e16c22131cd3823e.json.
Report an issue: GitHub.