如何在 Django 中过滤对象以进行计数注释?
How to filter objects for count annotation in 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
个注释值。
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 feature, see 。
更新 3. 对于 Django 3.x 请 .
更新
我提到的子查询方法现在通过 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`"
})
刚发现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()
)))
我建议改用 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)]
按 event
字段将所有 Participant
分组:
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}]>
.values
和 .distinct
在这里所做的是,他们正在创建两个按其元素 event
分组的 Participant
桶。请注意,这些桶包含 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
。
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 的单独部分。它与条件聚合相同,但更像我上面的示例。无论哪种方式,这都比我之前做的粗糙的子查询健康得多。
我要找的结果是什么:
- 将任务添加到报告中的人员(受让人)。 - 完全独特
人数
- 将任务添加到报告中但为了任务的人
可计费性仅大于 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不是User Model中的模型字段,这里我们过滤的是点赞数(或xyz)超过100的用户。
考虑简单的 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 feature, see
更新 3. 对于 Django 3.x 请
更新
我提到的子查询方法现在通过 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`"
})
刚发现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()
)))
我建议改用 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)]
按
event
字段将所有Participant
分组: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}]>
.values
和.distinct
在这里所做的是,他们正在创建两个按其元素event
分组的Participant
桶。请注意,这些桶包含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
。
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 的单独部分。它与条件聚合相同,但更像我上面的示例。无论哪种方式,这都比我之前做的粗糙的子查询健康得多。
我要找的结果是什么:
- 将任务添加到报告中的人员(受让人)。 - 完全独特 人数
- 将任务添加到报告中但为了任务的人 可计费性仅大于 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不是User Model中的模型字段,这里我们过滤的是点赞数(或xyz)超过100的用户。