在这里定义一些基本结构:
class A( Base ):
__tablename__ = 'a'
id = Column( types.Integer(), primary_key = True )
abs = orm.relation( 'AB', lazy='joined' )
class AB( Base ):
__tablename__ = 'ab'
id = Column( types.Integer(), primary_key = True )
a_id = Column( types.Integer(), ForeignKey( 'a.id' ) )
b_id = Column( types.Integer(), ForeignKey( 'b.id' ) )
a = orm.relation( 'A' )
b = orm.relation( 'B', lazy='joined' )
class B( Base ):
__tablename__ = 'b'
id = Column( types.Integer(), primary_key = True )
bas = orm.relation( 'AB' )
现在说我有A
个与B
相关的多个A.id = 1
,并且我想基于这些B
进行过滤。我做了以下查询:
a = db.session.query( A ).join( A.abs ).filter( AB.b_id = 1, A.id = 1 ).first()
此时,我希望len( a.abs ) == 1
,但事实并非如此。换句话说,应用于连接的过滤器不会传播到orm.relation。我该如何得到这种行为?
答案 0 :(得分:1)
The Zen of Eager Loading中描述了此问题的原因,其中包含以下内容:您的查询中将有两个不同的联接。一个用于构建正确的连接以进行过滤(这是您使用.join(A.abs)
生成的),另一个用于加载关系(ORM根据lazy="joined"
自动插入,否则它将查询它oncess)。
现在有几种解决方法。但首先你应该考虑你真正想要的东西。因为当你说A.abs
时,你真的说“所有属于这个A的AB条目”。但是当你指定一个b_id
而不是你想要的那个时,因为这不是这种关系所代表的含义。所以这是干净的方式:
db.session.query(A, AB).join(A.abs).filter(AB.b_id = 1, A.id = 1)
现在,您将AB
作为查询中的第二个返回对象。这是正确的方法,因为AB
中只有一个A.abs
实际上会对ORM撒谎:这里不是真的(它可能会破坏东西)。但是,如果你坚持这样做,那就有可能。您可以使用sqlalchemy.orm.contains_eager禁用双连接:
db.session.query(A).join(A.abs).options(contains_eager(A.abs)).filter(AB.b_id = 1, A.id = 1)
这将产生A.abs
只有一个条目b_id = 1
。但是,如前所述,这不是一个好的解决方案,而不是你应该做的。
作为补充提示,我建议您在引擎中启用echo=True
甚至echo="debug"
,以确保您看到正在执行的查询。如果您查看原始查询,那么将在同一个表上看到两个连接。