如何使用 Django ORM 从三个连接的模型中进行这个非常复杂的查询?

How to make this very complicated query from three connected models with Django ORM?

大家好。希望你做得很好。我是 Django 新手,在帮助一个小型应用程序项目的同时尝试学习 RESTful 开发的基础知识。我们目前希望我们的一些模型根据我们提交给它们的数据进行相应更新,通过使用 Django ORM 和其中一些模型与 OneToMany 关系共享的字段。目前,我必须执行一个非常困难的查询,以便我的字段之一根据该过滤器自动更新。首先,让我解释一下模型。这不是真的,而是一个应该工作相同的分身:

首先我们有一个 Report 模型,它是学生的老师报告:

class Report(models.Model): 

    status = models.CharField(max_length=32, choices=Statuses.choices, default=Statuses.created,)
    student = models.ForeignKey(Student, on_delete=models.CASCADE,)
    headroom_teacher = models.ForeignKey(TeacherStaff, on_delete=models.CASCADE,)

    # Various dates
    results_date = models.DateTimeField(null=True, blank=True)
    report_created = models.DateTimeField(null=True, blank=True)
    .
    #Other fields that don't matter

这里我们有两个相关的模型,分别是studentheadroom_teacher。没有必要展示他们的模型,但他们与接下来的两个模型的关系非常重要。我们还有一个 Exams 模型:

class Exams(models.Model):

    student = models.ForeignKey(student, on_delete=models.CASCADE,)     
    headroom_teacher = models.ForeignKey(TeacherStaff, on_delete=models.CASCADE,)
            
    # Various dates
    results_date = models.DateTimeField(null=True, blank=True)
    initial_exam_date = models.DateTimeField(null=True, blank=True)
    .
    #Other fields that don't matter

如您所见,此应用程序的目的类似于报告学生完成某些考试后的表现,并且每份报告都是由老师针对特定学生在这些考试中的表现而制作的。最后,我们有一个名为 StudentMood 的最终模型,旨在根据学生的考试状态显示学生的感受:

class StudentMood(models.Model):

    report = models.ForeignKey(Report, on_delete=models.CASCADE,)
    student_status = models.CharField(
        max_length=32, choices=Status.choices,
        default=None, null=True, blank=False)
    headroom_teacher = models.ForeignKey(TeacherStaff, on_delete=models.CASCADE,)

有了这三个模型,我们就到了问题的症结所在。我们可能的 student_status 选项之一称为 Anxious for results,我们相信学生在已经完成考试并正在等待结果的时候会有这种感觉。

我想自动将我的 student_status 设置为那个,使用自定义管理器,该管理器考虑了报告完成日期或数据输入日期。我相信这可以通过考虑 initial_exam_date.

进行查询来完成

我已经设置了自定义管理器,唯一缺少的就是这个查询。没办法只能用Django的ORM来做。但是,我提出了一个近似的原始 SQL 查询,我不确定它是否可以:

SELECT student_mood.id AS student_mood_id FROM
school_student_mood LEFT JOIN
school_reports report
ON student_mood.report_id = report.id AND student_mood.headroom_teacher_id = report.headroom_teacher_id
JOIN school_exams exams
ON report.headroom_teacher_id = exams.headroom_teacher_id 
AND report.student_id = exams.student_id
AND exams.results_date > date where the student_mood or report data is entered, I guess 

这就是我来寻求帮助的原因。有人可以阐明如何将其转换为单个查询吗?

没有环境设置或真正确切地知道您想要从数据中得到什么。这是一个好的开始。

一般来说,Django ORM 不适合这些类型的查询,尝试使用 select_related 或预取会导致非常复杂和低效的查询。

我发现在 Django 中实现这些类型的查询的最佳方法是将你的每一块拼图分解成一个查询 returns 然后你可以在其中使用的 ID“列表”一个子查询。

然后你继续努力直到你有你的最终输出

from django.db.models import Subquery

# Grab the students of any exams where the result_date is greater than a specific date.
student_exam_subquery = Exam.objects.filter(
    results_date__gt=timezone.now()
).values_list('student__id', flat=True)

# Grab all the student moods related to reports that relate to our "exams" where the student is anxious
student_mood_subquery = StudentMood.objects.filter(
    student_status='anxious',
    reports__student__in=Subquery(student_exam_subquery)
).values_list('report__id', flat=True)

# Get the final list of students
Student.objects.values_list('id', flat=True).filter(
    reports__id__in=Subquery(student_mood_subquery)
)

现在我怀疑这是否会开箱即用,但它确实是为了让您了解如何以未来开发人员可读且最有效(数据库明智)的方式解决此问题。

所以,我 运行 遇到的问题是,学校每个时期都有考试周期,很难仅检索该周期的学生报告。假设我们有以下数据库:

+-----------+-----------+----------------+-------------------+-------------------+------------------+
|  Student  | Report ID | StudentMood ID | Exam Cycle Status | Initial Exam Date | Report created a |
+-----------+-----------+----------------+-------------------+-------------------+------------------+
| Student 1 |         1 |              1 | Done              | 01/01/2020        | 02/01/2020       |
| Student 2 |         2 |              2 | Done              | 01/01/2020        | 02/01/2020       |
| Student 1 |         3 |              3 | On Going          | 02/06/2020        | 01/01/2020       |
| Student 2 |         4 |              4 | On Going          | 02/06/2020        | 01/01/2020       |
+-----------+-----------+----------------+-------------------+-------------------+------------------+

显然,我想将查询限制在这个周期内,如下所示:

+-----------+-----------+----------------+-------------------+-------------------+------------------+
|  Student  | Report ID | StudentMood ID | Exam Cycle Status | Initial Exam Date | Report created a |
+-----------+-----------+----------------+-------------------+-------------------+------------------+
| Student 1 |         3 |              3 | On Going          | 02/06/2020        | 01/01/2020       |
| Student 2 |         4 |              4 | On Going          | 02/06/2020        | 01/01/2020       |
+-----------+-----------+----------------+-------------------+-------------------+------------------+

现在,您的回答 trent 非常有用,但我在检索上述形状时仍然遇到问题:

qs_exams = Exams.objects.filter(initial_exam_date__gt=now()).values_list('student__id', flat=True)
qs_report = Report.objects.filter(student__id__in=qs_exams).values_list('id', flat=True)
qs_mood = StudentMood.objects.select_related('report') \
.filter(report__id__in=qs_report).order_by('report__student_id', '-created').distinct()

但是这个查询仍然给我整个学年的所有 StudentMoods。太棒了,有什么想法吗?