考虑简单的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。
答案 0 :(得分:88)
刚刚发现Django 1.8有新的conditional expressions feature,所以现在我们可以这样做:
height: 100px; /* over-ridden if vw can be interpreted */
height: 10vw; /* ignored if not understood */
答案 1 :(得分:46)
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的文档中有一个单独的部分。它与条件聚合相同,但更像我上面的例子。无论哪种方式,这比我以前做的粗糙的子查询更健康。
答案 2 :(得分:36)
<强>更新强>
我提到的子查询方法现在通过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`"
})
答案 3 :(得分:4)
我建议您使用Participant
查询集的.values
方法。
简而言之,您想要做的是:
Participant.objects\
.filter(is_paid=True)\
.values('event')\
.distinct()\
.annotate(models.Count('id'))
一个完整的例子如下:
创建2 Event
s:
event1 = Event.objects.create(title='event1')
event2 = Event.objects.create(title='event2')
向他们添加Participant
:
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
字段对所有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)...']>
这里需要与众不同:
Participant.objects.values('event').distinct()
> <QuerySet [{'event': 1}, {'event': 2}]>
.values
和.distinct
在这里做的是,他们正在创建两个由Participant
元组event
组成的存储桶。请注意,这些存储桶包含Participant
。
然后,您可以注释这些存储桶,因为它们包含原始Participant
的集合。在这里,我们要计算Participant
的数量,这可以通过计算这些桶中元素的id
来完成(因为它们是Participant
):
Participant.objects\
.values('event')\
.distinct()\
.annotate(models.Count('id'))
> <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
最后,您只希望Participant
is_paid
为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
。
答案 4 :(得分: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}]>