Как вернуть количество связанных объектов в запросе sqlalchemy

Я новичок в sqlalchemy, и, хотя документация кажется достаточно тщательной, я не мог найти способ сделать то, что мне нужно.

Скажем, у меня две таблицы: форум и сообщение. Каждый форум имеет родительский форум и любое количество сообщений. Я хочу:

  • Список форумов верхнего уровня
  • Довольно загруженные дочерние форумы, доступные через форумы верхнего уровня.
  • Количество сообщений для каждого дочернего форума

Итак, я начал с:

 query(Forum).filter(Forum.parent==None).all()

Который дает мне все форумы верхнего уровня. Конечно, доступ к дочерним форумам дает n запросов на выбор.

 query(Forum).options(eagerload('children')).filter(Forum.parent==None).all()

Это решает проблему n выбора.

Теперь мое лучшее предположение выглядит примерно так:

 query(Forum, func.count(Forum.children.posts)).options(eagerload('children')).filter(Forum.parent==None).group_by(Forum.children.id).all()

Но все, что я получаю, это:

AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object has an attribute 'posts'

Я пробовал несколько вариантов, но не получил. Для ясности я ищу эквивалент этого SQL:

select Forum.*, Child.*, count(Post.id)
from Forum
left join Forum Child on Child.parent = Forum.id
left join Message on Message.forum = Child.id
where Forum.parent is null
group by Child.id

Ответ 1

Поскольку вы хотите, чтобы почтовый счет был доступен на дочерних объектах форума, вам нужно объявить его как свойство столбца при настройке картографов. Объявление свойства столбца должно выглядеть примерно так (если вы используете декларативный):

Forum.post_count = column_property(select([func.count()],
        Message.__table__.c.forum == Forum.__table__.c.id
    ).correlate(Forum.__table__).as_scalar().label('post_count'),
    deferred=True)

Затем вы можете сформулировать свой запрос следующим образом:

query(Forum).filter_by(parent=None).options(
    eagerload('children'),
    undefer('children.post_count'))

Другой вариант - выбрать детей и подсчет отдельно. В этом случае вам нужно будет создать группу результатов:

ChildForum = aliased(Forum)
q = (query(Forum, ChildForum, func.count(Message.id))
        .filter(Forum.parent == None)
        .outerjoin((ChildForum, Forum.children))
        .outerjoin(ChildForum.posts)
        .group_by(Forum, ChildForum)
    )

from itertools import groupby
from operator import attrgetter

for forum, childforums in groupby(q, key=attrgetter('Node')):
    for _, child, post_count in childforums:
        if child is None:
            # No children
            break
        # do something with child