Вам нужна аннотация подзапроса над Course
.
. У вас есть отношение: 1 между Grades
и Course
, что означает, что может быть от 0 до n Grades
экземпляров, возвращенных дляодин Course
.Это все еще верно с точки зрения БД, даже если вы сводите его к курсам одного студента.
Бизнес-логика, вероятно, говорит, что вы можете иметь только один класс на курс и одного студента, но у вас нет таких ограничений наваша БД (вы можете добавить unique_together = ['course', 'student']
), но вам, вероятно, все еще нужно помнить этот факт для аннотации.
from django.db.models import OuterRef, Subquery, Max
# student => which you already have
# Max() added because the subquery must ever only return 1 hit
# otherwise an error is raised
grade_query = Grades.objects.filter(course=OuterRef('pk'), student=student).annotate(
max_grade=Max('grade')).values('max_grade')
Course.objects.annotate(
student_grade=Subquery(grade_query, output_field=CharField())
).values('name', 'student_grade')
Документы: https://docs.djangoproject.com/en/2.1/ref/models/expressions/#subquery-expressions
Операторы SQL / Производительность
Примечание о том, сколько операторов SQL запущено в зависимости от решения из-за комментариев.
Обычное ORM
for course in Course.objects.all():
grade = course.grades_set.filter(student=student).first() or ''
print(f'{course.name}: {grade}')
Это приводит к:
- 1 запрос для всех объектов курса
- + 1 для каждого объекта курса, чтобы получить оценки для этого студента и этого курса
Всего, если естьиз 20 курсов это приведет к 21 выбору SQL.
Предварительная выборка
Изменение Course.objects.all()
на предложение Дэниэла Роузмана (очень хороший!):
Course.objects.prefetch_related(
Prefetch('grades_set', queryset=Grade.objects.filter(student=my_student))
)
результатыв 2 SQL выбирает:
- 1 длякурсы
- 1 для всех оценок всех курсов для этого конкретного студента
Слой ORM обеспечивает объединение результатов обоих запросов.
(Многолучшее и наиболее гибкое решение.)
Аннотирование / агрегация
Результатом является ровно 1 выбор SQL.
Вам нужно заранее знать, чего вы хотите, и, если это производительностьявляется проблемой, и вы хотите избежать дополнительных SQL-запросов любой ценой, values()
гарантирует это, поскольку не дает никаких экземпляров Model, но простых значений, и только SELECT выбирает именно эти значения, а не что-нибудь еще (например, более связанные модели, которыевызывать ненужные объединения).
Если результат нужен только для отображения в шаблоне, извлечен в виде и не предназначен ни для чего другого, это хороший выбор.