我试图创造一个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
因此,在这种情况下,没有等级被定为法国,为此无级将被显示。
您可以通过使用prefetch_related
与受限制的queryset做到这一点。
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
相关等级。
你想要的是在Course
子查询注释。
你有N:Grades
和Course
之间的关系1,这意味着,有可能是0到一个Grades
返回ñ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语句的说明是根据,因为评论的解决方案触发。
for course in Course.objects.all():
grade = course.grades_set.filter(student=student).first() or ''
print(f'{course.name}: {grade}')
这导致:
产品总数,如果有20门课程,这将导致21种SQL选择。
更改Course.objects.all()
丹尼尔·罗斯曼的建议(非常好!):
Course.objects.prefetch_related(
Prefetch('grades_set', queryset=Grade.objects.filter(student=my_student))
)
结果在2种SQL选择:
ORM层负责合并这两个查询的结果。
(好多了,最灵活的解决方案。)
结果正好1 SQL选择。
你需要知道你事先想要的东西,如果性能是一个问题,你想避免额外的SQL选择不惜一切代价,values()
使得那肯定,因为它不会产生任何型号的实例,但简单的值,并且只选择这些人恰恰是值,而不是其他任何东西(如多个相关模型,引发不必要的连接)。
如果结果只需要在显示模板,在视图中获取,而不是意为别的,这是一个不错的选择。