SQLAlchemy works in layers. At the base layer, SQLAlchemy provides stuff such as a unified interface to databases using various database drivers, and a connection pool implementation. Above this sits a SQL Expression Language, allowing you to define the tables and columns of your database using Python objects, and then use those objects to create SQL expressions using the APIs that SQLAlchemy gives you. Then there is the ORM. The ORM builds on these existing layers, and so even if you use the ORM, you can still drop down to use the expression API. You are a level even above that, using the declarative model (which builds on the ORM).
Most of the expression API is based on the SQLAlchemy Table object and the columns. The tables are accessible by the __table__
property on the mapped class, and the columns are available as the properties on the mapped class. So, even though you are at the declarative level, you can still utilize much of what you have available to you there while using the models you mapped using declarative. So, the example correlated query...
>>> stmt = select([addresses.c.email_address]).\
... where(addresses.c.user_id == users.c.id).\
... limit(1)
>>> conn.execute(users.update().values(fullname=stmt))
...can translate to a declarative ORM model by using the __table__
attribute and declarative columns...
>>> stmt = select([Addresses.email_address]).\
... where(Addresses.user_id == Users.id).\
... limit(1)
>>> conn.execute(Users.__table__.update().values(fullname=stmt))
Here is what I believe your correlated query would look like..
stmt = select([Location.id]).\
where(and_(Location.x==Stopover.x, Location.y==Stopover.y)).limit(1)
conn.execute(Stopover.__table__.update().values(location_id=stmt)
The resulting SQL:
UPDATE stopovers SET location_id=(SELECT locations.id
FROM locations
WHERE locations.x = stopovers.x AND locations.y = stopovers.y
LIMIT ? OFFSET ?)