Upserts not honoring Column onupdate values #5903
-
Describe the bug Expected behavior To Reproduce import time
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy import *
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.sqlite import insert
engine = create_engine('sqlite://', echo=True)
session = Session(engine)
Base = declarative_base()
class MyPrices(Base):
__tablename__ = 'price_ath'
id = Column(Integer, primary_key=True, autoincrement=True)
token = Column(String, nullable=False, unique=True)
price = Column(Float, nullable=False)
timestamp = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
Base.metadata.create_all(engine)
session.add(MyPrices(token='1234', price=1.234))
session.commit()
print(session.query(MyPrices).first())
time.sleep(5)
stmt = insert(MyPrices).values(token='1234', price=3.14)
stmt = stmt.on_conflict_do_update(index_elements=['token'], set_=dict(price=stmt.excluded.price))
session.execute(stmt)
session.commit()
print(session.query(MyPrices).first()) Versions.
Additional context If there's a way to achieve a single-method "insert or update" in 1.3, I'm happy to use that. For simple situations, users should be able to use a single command (e.g. Have a nice day! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
This is a documented limitation of the various "on conflict" constructs:
SQLAlchemy has an assortment of "merge/upsert"-style constructs for various backends and they vary considerably in how they work, so for simplicity and avoidance of regressions / misleading behaviors the "onupdate" functions are currently omitted from the flow of these special-use constructs. The workaround is that one can add the "onupdate" function explicitly to the parameters passed in the set clause. Of particular concern is the notion of an "onupdate" function that would be inappropriate to invoke if an UPDATE does not actually take place. if someone passes an UPDATE statement with many parameter sets, SQLAlchemy's behavior right now is to run the Python-side "onupdate" construct once for each row, not just once. It wasn't clear if this behavior would be surprising or not for a series of parameters where many of the individual statement executions would ignore this generated value entirely. in the interests of making "upsert" constructs available without getting too bogged down with issues like these early on, "onupdate" and "default" were omitted from these constructs. Now that we have several "merge/upsert" constructs available we can look into beginning to support Python/SQL function onupdate/default constructs but I would want this to be considered for all supporting backends at once, and this would be a feature add. I would also not want it to be turned on by default until probably version 2.1, so there would need to be an option of some kind indicating these should be used.
SQLAlchemy has only added support for "upsert" for MySQL and PosgreSQL in very recent releases, and SQLite's is only in pre-release. There is no support for such constructs in other backends such as Oracle and SQL Server; these databases do support "upsert" but they do so via a very different syntax called MERGE, e.g. https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15 . It's the complexity of MERGE as well as how dramatically differently every backend does this that SQLAlchemy has been held back for many years in providing some kind of "abstraction" over this pattern, as users typically want to use the specific "upsert" construct of just one database. That's what we've provided so far, as it was decided that providing backend-specific constructs was better than providing no construct at all.
this would be the session.merge() feature. However, merge() does not make use of a SQL "upsert". #5441 addresses this feature request of providing for an ORM feature that integrates with those "upsert" features that are currently available.
|
Beta Was this translation helpful? Give feedback.
This is a documented limitation of the various "on conflict" constructs: