152

考虑简单的 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带注释的值。

文档中的示例在这里不起作用,因为它从查询中排除对象,而不是用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 具有新的条件聚合功能,请参阅下面接受的答案

更新 3。对于 Django 3.x,请查看下面的答案

4

6 回答 6

171

Django 2.0 中的条件聚合允许您进一步减少过去的 faff 数量。这也将使用 Postgres 的filter逻辑,它比 sum-case 快一些(我见过像 20-30% 这样的数字)。

无论如何,就您而言,我们正在研究以下简单的事情:

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

文档中有一个单独的部分关于过滤 annotations。它与条件聚合相同,但更像我上面的示例。无论哪种方式,这比我之前做的那些粗糙的子查询要健康得多。

于 2018-02-06T17:48:39.553 回答
95

刚刚发现 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()
    )))
于 2015-06-10T11:07:28.447 回答
46

更新

我提到的子查询方法现在通过subquery-expressions在 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()
    )
)

我更喜欢这个而不是聚合(sum+case),因为它应该更快更容易优化(使用适当的索引)

对于旧版本,同样可以使用.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`"
})
于 2015-06-10T09:58:27.263 回答
6

我建议改用.values您的查询集的方法Participant

简而言之,您想要做的是:

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

一个完整的例子如下:

  1. 创建 2Event秒:

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
    
  2. 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)]
    
  3. Participant按字段对所有 s 进行分组event

    Participant.objects.values('event')
    > <QuerySet [{'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, '...(remaining elements truncated)...']>
    

    这里需要 distinct:

    Participant.objects.values('event').distinct()
    > <QuerySet [{'event': 1}, {'event': 2}]>
    

    这里所做的是他们正在创建两个按元素分组的.valuess桶。请注意,这些存储桶包含..distinctParticipanteventParticipant

  4. 然后,您可以注释这些存储桶,因为它们包含一组 original Participant。在这里,我们要计算 的数量Participant,这只需通过计算id这些桶中元素的 s 来完成(因为那些是Participant):

    Participant.objects\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
    
  5. 最后你只想要Participant一个is_paidbeing True,你可以在前面的表达式前面添加一个过滤器,这会产生上面显示的表达式:

    Participant.objects\
        .filter(is_paid=True)\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 5}, {'event': 2, 'id__count': 25}]>
    

唯一的缺点是您必须在Event之后检索,因为您只有id上述方法中的。

于 2017-11-26T20:32:00.440 回答
1

我正在寻找什么结果:

  • 将任务添加到报表的人员(受让人)。- 独特的总人数
  • 已将任务添加到报告中的人员,但仅针对可计费性大于 0 的任务。

一般来说,我将不得不使用两个不同的查询:

Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()

但我想要两个都在一个查询中。因此:

Task.objects.values('report__title').annotate(withMoreThanZero=Count('assignee', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count('assignee', distinct=True))

结果:

<QuerySet [{'report__title': 'TestReport', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}, {'report__title': 'Utilization_Report_April_2019', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}]>
于 2019-06-20T09:48:40.520 回答
-1

对于 Django 3.x,只需在注释后编写过滤器:

User.objects.values('user_id')
            .annotate(xyz=models.Sum('likes'))
            .filter(xyz__gt=100)

在上面的 xyz 不是用户模型中的模型字段,这里我们过滤了喜欢(或 xyz)超过 100 的用户。

于 2021-12-05T11:54:42.807 回答