You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Allow user to define a subset of columns for update detection in UPSERT #3598
Currently, when detecting which rows should be updated in the upsert, all non-primary key columns are iterated over, converted to a Python type, and compared. This can be time-consuming and memory-consuming (e.g., with complex columns containing JSON data in struct, list, ...).
If the user knows that a change has occurred to specific columns, it would be a huge performance improvement to just iterate over these columns. Or if the user has a column that implies that any change has occurred (e.g., a hash of the data)
For example:
Hash column: if the user has the possibility to create a column containing a hash for each row, then upsert has to look only at this column when detecting changes. This way, pyiceberg doesn't have to convert all columns to Python type and compare them.
Proposition:
Update the function get_rows_to_update to also accept an optional parameter difference_cols. Update the upsert methods with this parameter and pass it to the get_rows_to_update.
In case there is no intersection between non-primary key columns and difference_cols, pyiceberg can either raise and error or it can fall-back to the default behaviour (iterating over all non-PK columns).
Usage:
frompyiceberg.schemaimportSchemafrompyiceberg.typesimportIntegerType, NestedField, StringTypeimportpyarrowaspaschema=Schema(
NestedField(1, "city", StringType(), required=True),
NestedField(2, "inhabitants", IntegerType(), required=True),
# Mark City as the identifier field, also known as the primary-keyidentifier_field_ids=[1]
)
tbl=catalog.create_table("default.cities", schema=schema)
arrow_schema=pa.schema(
[
pa.field("city", pa.string(), nullable=False),
pa.field("inhabitants", pa.int32(), nullable=False),
]
)
# Write some datadf=pa.Table.from_pylist(
[
{"city": "Amsterdam", "inhabitants": 921402},
{"city": "San Francisco", "inhabitants": 808988},
{"city": "Drachten", "inhabitants": 45019},
{"city": "Paris", "inhabitants": 2103000},
],
schema=arrow_schema
)
tbl.append(df)
df=pa.Table.from_pylist(
[
# Will be updated, the inhabitants has been updated
{"city": "Drachten", "inhabitants": 45505},
# New row, will be inserted
{"city": "Berlin", "inhabitants": 3432000},
# Ignored, already exists in the table
{"city": "Paris", "inhabitants": 2103000},
],
schema=arrow_schema
)
upd=tbl.upsert(df, difference_cols=["inhabitants"])
I have already prepared how it can look in my fork 59d1833.
If the proposition is accepted, I can prepare the PR.
Hi, I'd like to take this one. Planning to implement the proposal as described: add an optional difference_cols parameter to get_rows_to_update (defaulting to the current all-non-key-columns behavior) and thread it through Table.upsert / Transaction.upsert, with validation that the provided columns exist and aren't join columns. Note this affects update detection only — updated rows still write all columns. PR coming shortly.
Activity
minhduc29 commentedon Jul 7, 2026
Hi, I'd like to take this one. Planning to implement the proposal as described: add an optional
difference_colsparameter toget_rows_to_update(defaulting to the current all-non-key-columns behavior) and thread it throughTable.upsert/Transaction.upsert, with validation that the provided columns exist and aren't join columns. Note this affects update detection only — updated rows still write all columns. PR coming shortly.