【问题标题】:Django exclude from annotation countDjango 从注释计数中排除
【发布时间】:2015-10-29 10:03:55
【问题描述】:

我有以下申请:

from django.db import models


class Worker(models.Model):
    name = models.CharField(max_length=60)

    def __str__(self):
        return self.name


class Job(models.Model):
    worker = models.ForeignKey(Worker)
    is_completed = models.BooleanField()

我想用已完成作业的计数来注释 Workers 查询。

我将尝试使用以下脚本:

from myapp.models import Worker, Job
from django.db.models import Count

w = Worker.objects.create(name='Worker1')
Job.objects.create(worker=w, is_completed=False)
Job.objects.create(worker=w, is_completed=False)
Job.objects.create(worker=w, is_completed=True)
Job.objects.create(worker=w, is_completed=True)

workers = Worker.objects.all().annotate(num_jobs=Count('job'))
workers[0].num_jobs    
# >>> 4
workers = Worker.objects.all().exclude(job__is_completed=False).annotate(num_jobs=Count('job'))
# >>> []

最后一个查询的结果是空的。如何从反向关系中排除元素?

Django 1.8,python 2.7

UPD。我希望所有工作人员都在查询集中,即使是那些工作为零的工作人员

【问题讨论】:

  • this SO QA 值得一读。显然,注释所有记录并根据条件进行计数并不是那么简单

标签: python django django-models django-orm


【解决方案1】:

更新:好的,我用这个来生成解决方案,我想我用Conditional Expressions得到了它:

条件表达式让您可以在其中使用 if ... elif ... else 逻辑 过滤器、注释、聚合和更新。有条件的 表达式为表的每一行计算一系列条件 并返回匹配的结果表达式。

注意:Conditional Expressions(例如CaseWhen)是Django 1.8 中的新功能,正如@Pynchia 指出的那样

from django.db.models import IntegerField, Sum, Case, When

workers = Worker.objects.annotate(
              num_jobs=Sum(
                  Case(
                      When(job__is_completed=True, then=1),
                      default=0,  
                      output_field=IntegerField()
                  )
               )
           )

现在,每个工人都有一个 num_jobs ,它是一个整数,显示工人完成了多少工作:)

【讨论】:

  • 我已经尝试过您的解决方案,但它不起作用。它似乎计算了所有已完成的工作,它并没有注释所有的工人。请通过示例说明它的作用
  • @Pynchia 对我来说有效。你一定是做错了什么。
  • 哇,太棒了!我现在不能玩它,但它看起来是向前迈出的一大步。我认为您应该提到CaseWhen 条件表达式是新的,已在 Django 1.8 中引入
  • 还有一件事:您确定需要Q 运算符吗?根据文档中的示例,也许您可​​以摆脱它并仅使用When(job__is_completed=True, then=1),...
  • @Pynchia 是的,在没有 Q 对象的情况下也可以工作,只是对其进行了测试。更新了答案
【解决方案2】:

以下

workers = Worker.objects.filter(job__is_completed=True)).annotate(num_jobs=Count('job__is_completed'))

注释那些至少完成了一项工作的工人。 结果查询集中不包括那些完成了零个作业的作业。

如果您希望所有工作人员都出现在结果查询集中,如果我们可以编写会很棒

workers = Worker.objects.annotate(num_jobs=CountIf('job__is_completed', job__is_completed=True))

但不幸的是,我们不能。 所以我在这里超出了我的深度,我相信我的回答是片面的。 我欢迎比我更有能力的人的干预,他可以对此事有所了解。

有关参考,请参阅此Django proposed feature(已关闭)

this SO QA

更新: Django 1.8 引入了条件表达式。 @BogdiG 的回答使用这样的新运算符来解决问题。赞一个!

【讨论】:

    【解决方案3】:

    这里有 2 个选项。第一个是直接Q 过滤Count

    from myapp.models import Worker
    from django.db.models import Count, Q
    
    workers = Worker.objects.annotate(
        completed_jobs_count=Count("job", filter=Q(job__is_completed=True))
    )
    

    第二个是在Count 上排除Q 过滤器(有时需要它,因为Count 没有直接的exclude 选项):

    from myapp.models import Worker
    from django.db.models import Count, Q
    
    workers = Worker.objects.annotate(
        completed_jobs_count=Count("job", filter=~Q(job__is_completed=False))
    )
    
    

    【讨论】:

      【解决方案4】:

      更新

      如果您想统计每个工人的已完成工作,我们可以通过.extra() 使用子查询:

      Worker.objects.extra(select={'jobs_done': 
           'SELECT COUNT(*) FROM {job_tbl} WHERE worker_id = {worker_tbl}.id AND is_completed'
           .format(worker_tbl=Worker._meta.db_table, job_tbl=Job._meta.db_table)})
      

      很高兴 Django 现在支持@BogdiG's answer 中描述的语法中条件表达式SUM(CASE WHEN is_completed = True THEN 1 ELSE 0 END) 的 Python 映射。


      删除了关于filter/exclude的内容

      【讨论】:

      • 我删除了我的答案,因为它不会返回所有工人,而只会返回至少完成一项工作的工人。我相信您的解决方案也遇到了同样的问题。我相信 OP 希望用已完成工作的数量来注释所有工作人员,即使它是零。
      • @Pynchia 我明白了,那不是过滤器,更新我的答案。
      • 我重新发布了我的答案,但我不确定它是否满足问题。如果 OP 澄清他/她是否想要结果查询集中的所有工作人员,那就太好了
      猜你喜欢
      • 2023-02-14
      • 2014-12-25
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 2012-03-07
      • 2019-01-13
      • 1970-01-01
      • 2013-07-08
      相关资源
      最近更新 更多