Django исключить из числа аннотаций
У меня есть следующее приложение:
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()
Я хочу аннотировать запрос работников с количеством выполненных работ.
Я постараюсь сделать это с помощью следующего скрипта:
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'))
# >>> []
Результат последнего запроса пуст. Как исключить элементы из обратной связи?
Джанго 1.8, питон 2.7
UPD. Я хотел бы, чтобы все работники в queryset, даже те, кто имеет ноль рабочих мест
4 ответа
обновление: хорошо, я немного поиграл с этим, чтобы сгенерировать решение, и я думаю, что получил его, используя условные выражения:
Условные выражения позволяют использовать логику if... elif... else в фильтрах, аннотациях, агрегатах и обновлениях. Условное выражение оценивает ряд условий для каждой строки таблицы и возвращает соответствующее выражение результата.
Примечание. Условные выражения (например, Case
а также When
) являются новыми в 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, который будет целым числом, показывающим, сколько выполненных заданий у этого работника есть:)
Здесь есть 2 варианта. Первый - это прямой фильтр:
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))
)
А второй исключает
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))
)
Следующие
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 функцию (закрыто)
ОБНОВЛЕНИЕ: Django 1.8 ввел условные выражения. Ответ @BogdiG использует такие новые операторы для решения проблемы. Престижность!
Обновить
Если вы хотите подсчитать выполненную работу для каждого работника, мы могли бы использовать подзапрос через .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 теперь поддерживает отображение условных выражений в Python SUM(CASE WHEN is_completed = True THEN 1 ELSE 0 END)
в синтаксисе, описанном в ответе @BogdiG.
Удаленные вещи о filter/exclude