SQLAlchemy关联查询重复JOIN及contains_eager调用报错问题问询
所有问题的核心原因可以归为两类:一是模型关系配置不符合你「默认不自动加载关联」的需求,二是关联表过滤的写法不正确,导致SQLAlchemy重复引入users表。
第一步:先修正模型配置
你当前UserLocation模型的user关系设置了lazy='joined',这会导致所有查询UserLocation的请求默认自动join users表加载关联数据,完全违背你的需求,先修改为如下配置:
class UserLocation(Base): __tablename__ = "user_locations" # 其他原有字段保持不变 user = relationship( "User", back_populates="location", lazy="select", # 默认懒加载,仅主动访问user属性时才会发单独查询 # 若要彻底禁止隐式查询,避免不小心触发N+1,可替换为 lazy="raise",访问未显式加载的关联直接报错 foreign_keys=[user_id] )
User模型的location关系已经设置了lazy='noload',符合需求无需修改。
问题1:eager load查询报错
原因
原模型的UserLocation.user配置了lazy='joined',默认就会自动加载关联,此时再调用contains_eager会和默认加载逻辑冲突,触发SQLAlchemy内部的属性读取异常。
解决
修改模型配置后,用别名绑定的方式写查询即可:
from sqlalchemy.orm import aliased, contains_eager user_alias = aliased(models.User) return db\ .query(models.UserLocation) \ .join(user_alias, models.UserLocation.user) \ .filter(user_alias.title != None) \ .options(contains_eager(models.UserLocation.user, alias=user_alias)) \ .all()
问题2:无join过滤出现2个from子句
原因
你没有显式join users表,却直接用models.User.title做过滤,SQLAlchemy无法识别这个User表和UserLocation的关联,会直接把User表加到from子句生成笛卡尔积,再加上原模型默认lazy='joined'自动join的一次users表,就出现了两个User表的引入。
解决
只要用到关联表的过滤条件,必须显式写join,不要在无join的查询里加关联表的过滤条件。
问题3:显式join后出现两次JOIN
原因
两个问题叠加:一是原模型lazy='joined'默认自动join一次users表;二是过滤时直接用models.User.title,SQLAlchemy认为你需要额外引入一个独立的User表做过滤,就又新增了一次join。
解决
修改模型配置后,用别名绑定的方式写查询,过滤时用join生成的别名而不是直接用models.User,就能避免重复join,写法和问题1的查询示例一致。
内容的提问来源于stack exchange,提问作者Dirk

