如何过滤Django中count注释的对象?

2024-06-28 10:57:02 发布

您现在位置:Python中文网/ 问答频道 /正文

考虑简单的Django模型EventParticipant

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注释值。

这里的example from documentation不起作用,因为它从查询中排除对象,而不是用0注释对象。

更新。Django 1.8有了新的conditional expressions feature,所以现在我们可以这样做:

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具有新的Conditional aggregation功能,请参见下面的the accepted answer


Tags: djangoeventtruedefaultdbindexmodelis
3条回答

刚刚发现Django 1.8有新的conditional expressions feature,所以现在我们可以这样做:

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 2.0中的Conditional aggregation允许您进一步减少过去的faff数量。这也将使用Postgres'filter逻辑,这比求和的情况要快一些(我已经看到20-30%这样的数字出现在周围)。

不管怎样,在你的情况下,我们正在寻找一些简单的东西:

from django.db.models import Q, Count
events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

文档中有一个关于filtering on annotations的单独部分。这和条件聚合是一样的,但更像我上面的例子。不管是哪种方式,这都比我以前做的那些烦人的子查询要健康得多。

更新

我提到的子查询方法现在在Django 1.11中通过subquery-expressions支持。

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()
    )
)

比起聚合(sum+case),我更喜欢这种方法,因为它应该更快、更容易优化(使用适当的索引)

对于旧版本,也可以使用^{}来实现

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`"
})

相关问题 更多 >