如何用Django ORM高效实现课程与学生交叉连接并关联成绩?
优化Django中课程-学生成绩交叉表的查询性能
你的问题太典型了——当要生成这种课程×学生的二维成绩交叉表时,循环查询每一组(课程,学生)的成绩确实会踩中N+1查询的大坑(30个学生×50门课就是1500次查询!),完全可以用Django ORM的预取能力+内存映射来解决,下面给你两种高效的实现方式:
方案一:预取所有成绩,内存中构建映射表
这个方法只需要1次查询获取所有相关成绩,然后用字典把(学生ID,课程ID)映射到对应成绩,之后遍历课程-学生组合时直接查字典即可,彻底避免循环中的重复查询:
from itertools import product # 先获取目标班级的学生和需要展示的课程(假设你已经拿到classroom实例) students = Student.objects.filter(classroom=classroom).all() courses = Course.objects.all() # 可根据需求筛选特定课程 # 一次性拉取所有相关成绩,关联学生和课程(仅1次查询) grades = Grades.objects.filter( student__in=students, course__in=courses ).select_related('student', 'course') # 构建(student_id, course_id) -> grade的字典映射,内存中快速查找 grade_map = {(g.student_id, g.course_id): g.grade for g in grades} # 生成交叉表数据 cross_table_data = [] for course in courses: row = {"course_name": course.name} for student in students: # 用get方法处理无成绩的情况,返回空字符串 row[student.name] = grade_map.get((student.id, course.id), "") cross_table_data.append(row)
这种方式总共只需要3次查询(学生、课程、成绩),性能比原来的循环查询提升几个数量级。
方案二:用Django ORM直接生成结构化数据(进阶)
如果想更贴合Django ORM的风格,还可以用annotate结合Case/When,让数据库直接为每个学生生成对应的成绩字段,适合学生数量固定的小型班级场景:
from django.db.models import Case, When, Value, CharField students = Student.objects.filter(classroom=classroom).all() # 为每个学生创建一个Case/When子句,匹配他们的对应成绩 annotations = {} for student in students: annotations[f"student_{student.id}_grade"] = Case( When(grades__student=student, then="grades__grade"), default=Value(""), output_field=CharField() ) # 一次性查询所有课程及其对应每个学生的成绩(仅2次查询) course_grade_data = Course.objects.annotate( **annotations ).prefetch_related("grades__student").distinct() # 转换为交叉表格式 cross_table_data = [] for course in course_grade_data: row = {"course_name": course.name} for student in students: row[student.name] = getattr(course, f"student_{student.id}_grade") cross_table_data.append(row)
这个方法把成绩匹配逻辑下推到数据库处理,同样能彻底解决N+1问题。
两种方案的对比
- 方案一更灵活,适合学生或课程数量动态变化的场景,内存处理逻辑直观易懂;
- 方案二更贴近ORM风格,依赖数据库的聚合能力,适合学生数量固定的小型班级。
不管选哪种,都能把你的查询次数从1500+降到3次以内,性能提升非常明显。
内容的提问来源于stack exchange,提问作者MrDikke
相关产品推荐
相关产品推荐

