Сложная агрегация в Django

Использование Django rest framework 3.x и Django 1.1.10. У меня есть модель, которая представляет пользователей. Когда я перечисляю всех пользователей, обращаясь к конечной точке /users/ в DRF, список должен включать некоторые дополнительные данные, связанные с пользователями через другую модель, называемую владельцем. У каждого элемента есть владелец, а у владельцев есть пользователи.

Я сделал дополнительное свойство в модели User, и оно просто возвращает массив данных JSON. Это то, что я не могу изменить, потому что это требование внешнего интерфейса. Я должен вернуть общее количество элементов, связанных с каждым пользователем, и для получения данных нужно выполнить три разных подсчета.

Мне нужно получить несколько элементов count() для одной и той же модели, но с разными условиями.

Делать это по отдельности легко, два тривиальны, а последний сложнее:

Item.objects.filter(owner__user=self).count()
Item.objects.filter(owner__user=self, published=True).count()
Item.objects.filter(Q(history__action__name='argle') | Q(history__action__name='bargle'),
                    history__since__lte=now,
                    history__until__gte=now,
                    owner__user=self).count()

Проблема в том, что это запускается для каждого пользователя, а их много. В конце концов это генерирует более 300 запросов к БД, и я хотел бы свести их к минимуму.

Пока я придумал это:

Item.objects.filter(owner__user=self)\
            .aggregate(published=Count('published'),
                       total=Count('id'))

Это объединит первые два счетчика, вернет их, и в базе данных будет выполнено только одно SELECT. Есть ли способ включить последний вызов count() в тот же самый aggregate()?

Я пробовал много вещей, но это кажется невозможным. Должен ли я просто написать собственный SELECT и использовать Item.objects.raw()?

Я также заметил, что выполнение aggregate() и последнего count() выполняется быстрее на моем компьютере для разработки и SQLite, чем на промежуточном сервере с Postgresql, что немного странно, но сейчас это не моя главная задача.


person BigWhale    schedule 26.04.2017    source источник


Ответы (1)


Поскольку вам требуются подсчеты для каждого элемента в вашем наборе запросов, вы должны использовать in-a-queryset" rel="nofollow noreferrer">аннотировать вместо агрегата, тогда будет выполнен только 1 запрос.

Лучший способ подсчета связанных объектов на основе условия — использовать условная агрегация

User.objects.annotate(
    total_items=Count('items'),
    published=Sum(Case(When(items__published=True, then=1), output_field=IntegerField())),
    foo=Sum(Case(When(
        Q(history__action__name='argle') | Q(history__action__name='bargle'),
        history__since__lte=now,
        history__until__gte=now,
        then=1
    ), output_field=IntegerField()))
)
person Iain Shelvington    schedule 26.04.2017
comment
О, это блестяще. Да, аннотировать каждого пользователя количеством элементов — это то, что нужно. Я был слишком сосредоточен на самих предметах. Спасибо! - person BigWhale; 26.04.2017