Аннотации 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. Это проще всего понять.
Ответы
Ответ 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, где обсуждается порядок фильтрации.