联接上的过滤器不会传播到相关的orm.relations

时间:2013-09-18 15:42:41

标签: python sqlalchemy

在这里定义一些基本结构:

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。我该如何得到这种行为?

1 个答案:

答案 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",以确保您看到正在执行的查询。如果您查看原始查询,那么将在同一个表上看到两个连接。