Подтвердить что ты не робот

Аннотации Django с вложенным фильтром

Можно ли фильтровать в аннотации?

В моем сознании что-то вроде этого (что на самом деле не работает)

Student.objects.all().annotate(Count('attendance').filter(type="Excused"))

В итоговой таблице будет каждый студент с количеством пропущенных абзацев. Просмотр фильтров документации может быть только до или после аннотации, которая не даст желаемых результатов.

Обходной путь - это

for student in Student.objects.all():
    student.num_excused_absence = Attendance.objects.filter(student=student, type="Excused").count()

Это работает, но делает много запросов, в реальном приложении это может оказаться непрактично длинным. Я думаю, что этот тип утверждения возможен в SQL, но, если это возможно, предпочтет остаться с ORM. Я даже попытался сделать два отдельных запроса (один для всех студентов, другой - для получения итогового значения) и объединить их с |. Комбинация изменила общее количество: (

Некоторые мысли после чтения ответов и комментариев

Я решил проблему присутствия, используя дополнительный sql здесь.

  • Сообщение блога Тимми было полезно. Мой ответ основан на этом.
  • Ответ hash1baby работает, но кажется столь же сложным, как sql. Он также требует выполнения sql, а затем добавления результата в цикл for. Это плохо для меня, потому что я собираю много этих фильтрующих запросов вместе. Мое решение создает большой набор запросов с большим количеством фильтров и дополнительных и выполняет все это сразу.
  • Если производительность не проблема, я предлагаю работать цикл for. Это проще всего понять.
4b9b3361

Ответ 1

По Django 1.8 вы можете сделать это прямо в ORM:

students = Student.objects.all().annotate(num_excused_absences=models.Sum(
    models.Case(
        models.When(absence__type='Excused', then=1),
    default=0,
    output_field=models.IntegerField()
)))

Ответ адаптирован из другого вопроса SO по той же теме

Я не тестировал образец выше, но делал что-то подобное в своем приложении.

Ответ 2

Вы правы - django не позволяет вам фильтровать связанные объекты, которые подсчитываются, без применения фильтра к основным объектам и, следовательно, исключая эти первичные объекты без связанных объектов после фильтрации.

Но при небольшой утечке абстракции вы можете рассчитывать группы, используя запрос значений.

Итак, я собираю абзацы в словаре и использую это в цикле. Что-то вроде этого:

# a query for students
students = Students.objects.all()
# a query to count the student attendances, grouped by type.
attendance_counts = Attendence(student__in=students).values('student', 'type').annotate(abs=Count('pk'))
# regroup that into a dictionary {student -> { type -> count }}
from itertools import groupby
attendance_s_t = dict((s, (dict(t, c) for (s, t, c) in g)) for s, g in groupby(attendance_counts, lambda (s, t, c): s))
# then use them efficiently:
for student in students:
    student.absences = attendance_s_t.get(student.pk, {}).get('Excused', 0)

Ответ 3

Возможно, это сработает для вас:

excused = Student.objects.filter(attendance__type='Excused').annotate(abs=Count('attendance'))

Вам нужно отфильтровать учеников, которых вы ищете сначала, только тех, у кого есть оправданные абзацы, а затем аннотировать их количество.

Здесь ссылка на Django Aggregation Docs, где обсуждается порядок фильтрации.