考虑简单的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
。
文档中的示例在这里不起作用,因为它从查询中排除对象,而不是用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有了新的条件聚合特性,请参见下面的the accepted answer。这在Django 3.x中也有效
6条答案
按热度按时间velaa5lx1#
Django2.0中的条件聚合允许你进一步减少过去的浪费,这也将使用Postgres的
filter
逻辑,它比sum-case快一些(我见过20-30%这样的数字)。不管怎样,就你的情况而言,我们要考虑的事情很简单:
在文档中有一个单独的部分是关于注解过滤的。它和条件聚合是一样的,但更像我上面的例子。无论哪种方式,这都比我以前做的粗糙的子查询健康得多。
41zrol4v2#
刚刚发现Django 1.8有了新的conditional expressions feature,所以现在我们可以这样做:
qyswt5oh3#
更新
我提到的子查询方法现在在Django 1.11中通过子查询表达式得到了支持。
我更喜欢这种方法而不是聚合 (sum+case),因为它应该更快更容易优化 (使用适当的索引)。
对于旧版本,可以使用
.extra
实现相同的功能57hvy0tb4#
我建议使用
Participant
查询集的.values
方法。简而言之,您要执行的操作由以下公式给出:
完整示例如下:
1.创建2个
Event
:1.将
Participant
添加到它们:1.按
event
字段对所有Participant
进行分组:这里需要区分:
.values
和.distinct
在这里所做的是创建两个按元素event
分组的Participant
存储桶,注意这些存储桶包含Participant
。1.然后你可以注解这些桶,因为它们包含了原始的
Participant
集合。这里我们想要计算Participant
的个数,这可以通过计算这些桶中元素的id
来完成(因为它们是Participant
):1.最后,您只需要
Participant
,其中is_paid
为True
,您可以在前面的表达式前面添加一个过滤器,这将生成上面所示的表达式:唯一的缺点是,您必须在以后检索
Event
,因为您只有来自上面方法的id
。qf9go6mv5#
对于Django 3.x,只需在注解后写上filter:
在上面的
sudo_field
不是用户模型中的模型字段,在这里我们过滤喜欢(或xyz)超过100的用户。ma8fv8wu6#
我期待的结果:
通常,我必须使用两个不同的查询:
但我希望在一个查询中同时使用这两个参数。因此:
结果: