Создание левого соединения в Django с нулевыми значениями - PullRequest
0 голосов
/ 01 февраля 2019

Я пытаюсь создать Django-эквивалент SQL LEFT OUTER JOIN.Однако у меня возникли проблемы с этим.

class Course(models.Model):
    name = models.CharField(max_length=255)

class Grades(models.Model):
    grade = models.CharField(max_length=3)
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)

Моя цель состоит в том, чтобы пройтись по всем курсам, а когда есть ценность для студента (который у меня уже есть), затем распечатать его / ееоценка.Мне кажется, это действительно легко, но я просто не могу понять ...

РЕДАКТИРОВАТЬ:

Немного больше объяснений: у меня просто есть идентификатор студента, это то, что яподразумевается, что у меня уже есть.Мне нужно распечатать все курсы в таблице (которая находится в шаблоне)

Таким образом, вы получите:

Английский - 8,0
Французский -
Немецкий - 10

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

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Вы можете сделать это, используя prefetch_related с ограниченным набором запросов.

from django.db.models import Prefetch
courses = Course.objects.prefetch_related(
    Prefetch('grades_set', queryset=Grade.objects.filter(student=my_student))
)

Теперь, когда вы выполняете итерации по курсам, course.grade_set.all() будет содержать только оценки, связанные с my_student.

0 голосов
/ 01 февраля 2019

Вам нужна аннотация подзапроса над 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 выбирает именно эти значения, а не что-нибудь еще (например, более связанные модели, которыевызывать ненужные объединения).

Если результат нужен только для отображения в шаблоне, извлечен в виде и не предназначен ни для чего другого, это хороший выбор.

...