混合属性表达式:一对多的多边聚合属性

时间:2019-04-20 16:57:23

标签: python sql postgresql sqlalchemy

假设这些模型:

class A(Base):
    ...
    targets = relationship("B", back_populates='a')
    ...

class B(Base):
    ...
    a_id = Column(Integer, ForeignKey('a.id'))
    a = relationship("A", back_populates='targets')
    attr = Column(ENUM('a', 'b', 'c', name='options'), default='a')
    ...

如果所有相关的hybrid_attribute.expressionA中具有B属性值,我如何编写attr返回所有('b', 'c')的True? (如果没有相关的B,也应该返回False。)

这使我半途而废:

# hybrid_attribute expression in A model
@example_attr.expression
def example_attr(cls):
    return case(
        [
            (
                B.attr.in_(('b','c')),
                True
            )
        ],
        else_=False
    )

但是如何将它们按A.id分组,如果有任何相关的BTrue,那么该example_attr行的A列值就是True

我希望top能够做到:session.query(A).filter(A.example_attr.is_(True)).all()

编辑1:

此SQL似乎提供了所需的结果:

select a.id, coalesce(bool_or(a_id_b_attr.status), false)
    from a left outer join (
        select b.a_id as a_id, b.attr in ('b', 'c') as status from b
    ) as a_id_b_attr
    on a.id = a_id_b_attr.a_id group by a.id;

但是我在引用子查询中的别名字段时遇到了一个小问题:

sub = db.session.query(B.a_id.label('a_id'), B.attr.in_(('b', 'c')).label('status')).subquery() 
db.session.query( 
    A.id,  
    db.func.coalesce( 
        db.func.bool_or(sub.status),  
        False 
    ) 
).\ 
outerjoin(sub, A.id == sub.key_id).\ 
group_by(A.id) 

抛出AttribubteError的原因是sub没有任何别名属性。

1 个答案:

答案 0 :(得分:3)

  

我如何编写hybrid_attribute.expression来为所有As返回True?如果任何相关B在('b', 'c')中具有attr属性值? (如果没有相关的B,它也应该返回False。)

您可以使用EXISTS子查询表达式:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    targets = relationship("B", back_populates='a')

    @hybrid_property
    def example_attr(self):
        return any(b.attr in {'b', 'c'} for b in self.targets)

    @example_attr.expression
    def example_attr(cls):
        return exists().\
            where(and_(B.a_id == cls.id,
                       B.attr.in_(['b', 'c']))).\
            correlate(cls)

class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey('a.id'))
    a = relationship("A", back_populates='targets')
    attr = Column(ENUM('a', 'b', 'c', name='options'), default='a')

这不依赖任何分组,而是相关性,可以直接在所需的查询中使用:

# Note that comparing booleans with booleans is a bit redundant
session.query(A).filter(A.example_attr).all()

Postgresql能够使用相关的EXISTS子查询表达式将(某些)查询重写为semijoins,因此有关相关性的常规性能注释可能不适用。

如果您想通过分组来解决这个问题,可以使用bool_or和显式联接:

session.query(A).\
    join(B).\
    group_by(A.id).\
    having(func.bool_or(B.attr.in_(['b', 'c']))).\
    all()

Postgresql允许在此分组查询中选择A的所有列,即使它们不是聚合表达式,因为它们在功能上依赖于分组列(换句话说,A.id确定了所选列)列。