如何在Django中过滤计数注解对象?

cyej8jka  于 2022-12-14  发布在  Go
关注(0)|答案(6)|浏览(125)

考虑简单的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有了新的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中也有效

velaa5lx

velaa5lx1#

Django2.0中的条件聚合允许你进一步减少过去的浪费,这也将使用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))
)

在文档中有一个单独的部分是关于注解过滤的。它和条件聚合是一样的,但更像我上面的例子。无论哪种方式,这都比我以前做的粗糙的子查询健康得多。

41zrol4v

41zrol4v2#

刚刚发现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()
    )))
qyswt5oh

qyswt5oh3#

更新

我提到的子查询方法现在在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`"
})
57hvy0tb

57hvy0tb4#

我建议使用Participant查询集的.values方法。
简而言之,您要执行的操作由以下公式给出:

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

完整示例如下:
1.创建2个Event

event1 = Event.objects.create(title='event1')
event2 = Event.objects.create(title='event2')

1.将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)]

1.按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)...']>

这里需要区分:

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

.values.distinct在这里所做的是创建两个按元素event分组的Participant存储桶,注意这些存储桶包含Participant
1.然后你可以注解这些桶,因为它们包含了原始的Participant集合。这里我们想要计算Participant的个数,这可以通过计算这些桶中元素的id来完成(因为它们是Participant):

Participant.objects\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))
> <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>

1.最后,您只需要Participant,其中is_paidTrue,您可以在前面的表达式前面添加一个过滤器,这将生成上面所示的表达式:

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

qf9go6mv

qf9go6mv5#

对于Django 3.x,只需在注解后写上filter:

User.objects.values('user_id')
            .annotate(sudo_field=models.Count('likes'))
            .filter(sudo_field__gt=100)

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

ma8fv8wu

ma8fv8wu6#

我期待的结果:

  • 已将任务添加到报表的人员(受分配人)。-人员的唯一总计数
  • 将任务添加到报表中,但仅限于可开单性大于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}]>

相关问题