在Django中使用空值创建左联接

时间:2019-02-01 17:13:30

标签: python django python-3.x django-models

我试图创建一个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 :(得分:1)

您想要的是Course上的子查询注释。

您在GradesCourse之间具有n:1的关系,这意味着对于一个Grades,可能返回0到n个Course实例。从DB的角度来看,这仍然是正确的,即使您将其简化为一个学生的课程。

业务逻辑可能说每个课程和一个学生只能获得一个年级,但是对数据库没有这种限制(可以添加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()更改为Daniel Roseman的建议(非常好!):

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准确地选择这些值,而不会选择其他任何值(例如触发不必要的联接的更多相关模型)。

如果仅在显示模板中需要结果,在视图中获取结果,而不将其用于其他任何事情,则是不错的选择。

答案 1 :(得分:1)

您可以通过在受限制的查询集中使用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相关的成绩。