问题
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