выражение гибридного атрибута: совокупный атрибут со многих сторон от одного ко многим - PullRequest
1 голос
/ 20 апреля 2019

при условии, что эти модели:

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.expression для возврата True для всех A s, где любые связанные B s имеют значение атрибута attr в ('b', 'c')? (Это также должно вернуть False, если нет связанных B s.)

Это приводит меня на полпути:

# 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, если, если любые связанные B s равны True, тогда значение столбца 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 Ответ

3 голосов
/ 24 апреля 2019

Как я мог бы написать hybrid_attribute.expression для возврата True для всех Как и где любые связанные B имеют значение атрибута attr в ('b', 'c')?(Это также должно возвращать False, если нет связанных B.)

Вы можете использовать выражение подзапроса 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 определяет выбранные столбцы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...