Как фильтровать объекты для подсчета аннотаций в Django?



рассмотрим простые модели Django Event и Participant:



class Event(models.Model):
title = models.CharField(max_length=100)

class Participant(models.Model):
event = models.ForeignKey(Event, db_index=True)
is_paid = models.BooleanField(default=False, db_index=True)


легко комментировать события запроса с общим количеством участников:



events = Event.objects.all().annotate(participants=models.Count('participant'))


как комментировать с количеством участников отфильтрованных по is_paid=True?



мне нужно, чтобы запрос все события независимо от количества участников, например, мне не нужно фильтровать по аннотированной результат. Если есть 0 участники, все в порядке, мне просто нужно 0 in аннотированное значение.



The пример из документации здесь не работает, потому что он исключает объекты из запроса вместо аннотирования их с 0.



обновление. Django 1.8 имеет новый функция условных выражений, так что теперь мы можем сделать такой:



events = Event.objects.all().annotate(paid_participants=models.Sum(
models.Case(
models.When(participant__is_paid=True, then=1),
default=0,
output_field=models.IntegerField()
)))


обновление 2. Django 2.0 имеет новый условное агрегации функции см. В разделе принято отвечать под.

717   4  

4 ответов:

условное агрегации в Django 2.0 позволяет дополнительно уменьшить количество faff это было в прошлом. Это также будет использовать Postgres' filter логика, которая несколько быстрее, чем сумма-случай (я видел цифры, как 20-30% разбросаны вокруг).

в любом случае, в вашем случае мы смотрим на что-то простое, как:

events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

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

только что обнаружил, что Django 1.8 присвоен новый функция условных выражений, Так что теперь мы можем сделать такой:

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0, output_field=models.IntegerField()
    )))

обновление

подход подзапроса, который я упоминаю, теперь поддерживается в Django 1.11 через подзапрос-выражения.

Event.objects.annotate(
    num_paid_participants=Subquery(
        Participant.objects.filter(
            is_paid=True,
            event=OuterRef('pk')
        ).values('event')
        .annotate(cnt=Count('pk'))
        .values('cnt'),
        output_field=models.IntegerField()
    )
)

Я предпочитаю это агрегации (сумма+чехол), потому что это должно быть быстрее и легче быть оптимизированы (С соответствующей индексацией).

для более старой версии, то же самое может быть достигнуто с помощью .extra

Event.objects.extra(select={'num_paid_participants': "\
    SELECT COUNT(*) \
    FROM `myapp_participant` \
    WHERE `myapp_participant`.`is_paid` = 1 AND \
            `myapp_participant`.`event_id` = `myapp_event`.`id`"
})

я бы предложил использовать .values метод Participant вместо queryset.

короче говоря, то, что вы хотите сделать, дается:

Participant.objects\
    .filter(is_paid=True)\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))

полный пример выглядит следующим образом:

  1. создать 2 Events:

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
    
  2. добавить Participant s им:

    part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))\
              for _ in range(10)]
    part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))\
              for _ in range(50)]
    
  3. группа ParticipantС их

Comments

    Ничего не найдено.