带有嵌套过滤器的Django注释

时间:2011-01-06 22:00:16

标签: 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 here解决了出勤问题。

  • Timmy's blog post很有用。我的答案是基于它的。
  • hash1baby的答案有效,但似乎与sql一样复杂。它还需要执行sql然后在for循环中添加结果。这对我不好,因为我将大量这些过滤查询堆叠在一起。我的解决方案构建了一个包含大量过滤器和额外内容的大型查询集,并立即执行所有操作。
  • 如果性能不成问题 - 我建议for循环工作。到目前为止,这是最容易理解的。

3 个答案:

答案 0 :(得分:15)

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

根据another SO question on the same topic

改编的答案

我还没有对上面的示例进行测试,但确实在我自己的应用程序中完成了类似的工作。

答案 1 :(得分:5)

你是对的 - 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)

答案 2 :(得分:-3)

也许这对你有用:

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

您需要首先过滤您正在寻找的学生,然后过滤那些有缺席的学生,然后注明他们的数量。

以下是Django Aggregation Docs的链接,其中讨论了过滤顺序。