ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Django 中过滤对象以进行计数注释?

考虑简单的 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

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


R
Reinout van Rees

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 的单独部分。它与条件聚合相同,但更像我上面的示例。无论哪种方式,这比我之前做的那些粗糙的子查询要健康得多。


顺便说一句,文档链接中没有这样的示例,只显示了 aggregate 的用法。您是否已经测试过此类查询? (我没有,我想相信!:)
我有。他们工作。实际上,我遇到了一个奇怪的补丁,其中一个旧的(超级复杂的)子查询在升级到 Django 2.0 后停止工作,我设法用一个超级简单的过滤计数替换它。有一个更好的文档内注释示例,所以我现在将其拉入。
这里有几个答案,这是 Django 2.0 方式,下面你会找到 Django 1.11(子查询)方式和 Django 1.8 方式。
请注意,如果您在 Django <2,例如 1.9 中尝试此操作,它将毫无例外地运行,但过滤器根本没有应用。所以它可能看起来适用于 Django <2,但不是。
如果您需要添加多个过滤器,您可以将它们添加到 Q() 参数中,并用 分隔,例如 filter=Q(participants__is_paid=True, somethingelse=value)
D
Dan

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

当匹配项目很多时,这是一个合格的解决方案吗?假设我想计算最近一周发生的点击事件。
为什么不?我的意思是,为什么你的情况不同?在上述情况下,可以有任意数量的付费参与者参加活动。
我认为@SverkerSbrg 提出的问题是这对于大型集合是否效率低下,而不是它是否会起作用......对吗?要知道的最重要的事情是它不是在 python 中执行它,它是创建一个 SQL 案例子句 - 参见 github.com/django/django/blob/master/django/db/models/… - 所以它的性能相当不错,简单的例子比连接更好,但更复杂的版本可能包括子查询等等
当将此与 Count(而不是 Sum)一起使用时,我想我们应该设置 default=None(如果不使用 django 2 filter 参数)。
T
Todor

更新

我提到的子查询方法现在通过 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 中修复,所以我猜您使用的是该版本,这就是它为您工作的原因。您可以阅读有关该 herehere 的更多信息
我知道当它应该为 0 时,它会产生一个 None。还有其他人得到这个吗?
@StefanJCollier 是的,我也得到了 None。我的解决方案是使用 Coalesce (from django.db.models.functions import Coalesce)。您可以这样使用它:Coalesce(Subquery(...), 0)。不过,可能有更好的方法。
这很棒,因为下面 Oli 更赞成的答案中的方法在可读性方面“更好”,导致 MySQL 上的“LEFT OUTER JOIN”。这在性能方面非常不友好。所以赞成两个答案!
R
Raffi

我建议改用您的 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') > 这里需要 distinct:Participant.objects.values('event').distinct() > 什么 .values 和 .distinct在这里所做的是,他们正在创建两个按元素事件分组的参与者桶。请注意,这些存储桶包含参与者。然后,您可以注释这些存储桶,因为它们包含原始参与者集。这里我们要计算 Participant 的数量,这只需计算这些桶中元素的 id(因为它们是 Participant): Participant.objects\ .values('event')\ .distinct()\ .annotate (models.Count('id')) > 最后你只想要一个参与者is_paid 为 True,您可以在前一个表达式前面添加一个过滤器,这会产生如上所示的表达式: Participant.objects\ .filter(is_paid=True)\ .values('event')\ .distinct()\ .annotate(models.Count('id')) >

唯一的缺点是您必须在之后检索 Event,因为您只有上述方法中的 id


aggregation docs 还讨论了 values()annotate() 的使用。
A
Arindam Roychowdhury

我正在寻找什么结果:

将任务添加到报表的人员(受让人)。 - 独特的总人数

已将任务添加到报告中的人员,但仅针对可计费性大于 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}]>

这是对问题的回答吗?它看起来更像是一个新问题,然后您自己回答,这与原始问题有些相关(解决方案使用一些相同的工具),但我不确定这个答案是否会为现有的答案集添加新的东西...
D
Deepanshu Mehta

对于 Django 3.x,只需在注释后编写过滤器:

User.objects.values('user_id')
            .annotate(xyz=models.Count('likes'))
            .filter(xyz__gt=100)

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


这看起来不像是这个问题的答案。它涉及注释和过滤,但最初的问题是关于计数和过滤被计数的对象,而不是根据结果计数或总和进行过滤。