Как фильтровать объекты для подсчета аннотаций в 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 имеет новый условное агрегации функции см. В разделе принято отвечать под.
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() ) )Я предпочитаю это агрегации (сумма+чехол), потому что это должно быть быстрее и легче быть оптимизированы (С соответствующей индексацией).
для более старой версии, то же самое может быть достигнуто с помощью
.extraEvent.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'))полный пример выглядит следующим образом:
создать 2
Events:event1 = Event.objects.create(title='event1') event2 = Event.objects.create(title='event2')добавить
Participants им: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)]группа
ParticipantС их
Comments