考虑简单的 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
。
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。
更新 3。 对于 Django 3.x,请check this answer below。
Django 2.0 中的 Conditional aggregation 允许您进一步减少过去的繁琐工作。这也将使用 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))
)
文档中有关于 filtering on annotations 的单独部分。它与条件聚合相同,但更像我上面的示例。无论哪种方式,这比我之前做的那些粗糙的子查询要健康得多。
刚刚发现 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()
)))
Count
(而不是 Sum
)一起使用时,我想我们应该设置 default=None
(如果不使用 django 2 filter
参数)。
更新
我提到的子查询方法现在通过 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`"
})
.extra
的方法,因为我更喜欢在 Django 中避免使用 SQL :) 我会更新这个问题。
Django 1.8.2
中修复,所以我猜您使用的是该版本,这就是它为您工作的原因。您可以阅读有关该 here 和 here 的更多信息
None
。我的解决方案是使用 Coalesce
(from django.db.models.functions import Coalesce
)。您可以这样使用它:Coalesce(Subquery(...), 0)
。不过,可能有更好的方法。
我建议改用您的 Participant
查询集的 .values
方法。
简而言之,您想要做的是:
Participant.objects\
.filter(is_paid=True)\
.values('event')\
.distinct()\
.annotate(models.Count('id'))
一个完整的例子如下:
创建 2 个事件: event1 = Event.objects.create(title='event1') event2 = Event.objects.create(title='event2') 向其中添加参与者: 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.objects.values('event') >
唯一的缺点是您必须在之后检索 Event
,因为您只有上述方法中的 id
。
我正在寻找什么结果:
将任务添加到报表的人员(受让人)。 - 独特的总人数
已将任务添加到报告中的人员,但仅针对可计费性大于 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}]>
对于 Django 3.x,只需在注释后编写过滤器:
User.objects.values('user_id')
.annotate(xyz=models.Count('likes'))
.filter(xyz__gt=100)
在上面的 xyz 不是用户模型中的模型字段,这里我们过滤了喜欢(或 xyz)超过 100 的用户。
不定期副业成功案例分享
aggregate
的用法。您是否已经测试过此类查询? (我没有,我想相信!:)