Django exclude from annotation count

两盒软妹~` 提交于 2019-12-07 03:30:25

问题


I have following application:

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()

I want to annotate Workers query with count of completed jobs.

I'll try to do it with following script:

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'))
# >>> []

Result of the last query is empty. How to exclude elements from reverse relation?

Django 1.8, python 2.7

UPD. I would like to have all workers in queryset, even those, who has a zero jobs


回答1:


update: ok I played a bit with this to generate the solution and I think I got it using Conditional Expressions:

Conditional expressions let you use if ... elif ... else logic within filters, annotations, aggregations, and updates. A conditional expression evaluates a series of conditions for each row of a table and returns the matching result expression.

Note: Conditional Expressions (such as Case and When) are new in Django 1.8, as pointed out by @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()
                  )
               )
           )

now, each worker will have a num_jobs which will be an Integer that shows how many completed jobs that worker has :)




回答2:


The following

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

annotates those workers who have at least one job completed. Those whth a count of zero job completed are not included in the result queryset.

In case you want ALL workers to appear in the result queryset, it would be great if we could write

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

but unfortunately, we cannot. So I am out of my depth here and I believe my answer is partial. I welcome the intervention of somebody more competent then I am who could shed some light on the matter.

For reference, see this Django proposed feature (closed)

and this SO QA

UPDATE: Django 1.8 introduced conditional expressions. @BogdiG's answer uses such new operators to solve the problem. Kudos!




回答3:


Update

If you want a count of completed job for every worker, than we could use subquery through .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)})

It's great that Django now supports the Python mapping of conditional expression SUM(CASE WHEN is_completed = True THEN 1 ELSE 0 END) in syntax described in @BogdiG's answer.


Removed things about filter/exclude



来源:https://stackoverflow.com/questions/31852672/django-exclude-from-annotation-count

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!