Django Order By on Reverse Foreign Key value 删除没有关系的项目
Django Order By on Reverse Foreign Key value drops items without relationships
上下文
我正在尝试根据反向外键关系的值对对象列表进行排序。
我有部分解决方案,但使用的过滤器会创建一个内部联接,该联接会删除没有关系的对象。
结果我想用所有对象创建排序的查询集,威胁"null" 反向关系,就好像相关值是空的。
型号
class Student(models.Model):
name = models.CharField(max_length=128)
class Subject(models.Model):
title = models.CharField(max_length=128)
class Grade(models.Model):
student = models.ForeignKey("Student", related_name="grades", on_delete=models.CASCADE)
subject = models.ForeignKey("Subject", related_name="grades", on_delete=models.CASCADE)
value = models.IntegerField()
赛程
+------+------------------------+
| name | subject | grade_value |
+------+----------+-------------+
| beth | math | 100 |
| beth | history | 100 |
| mark | math | 90 |
| mark | history | 90 |
| mike | history | 80 |
+------+----------+-------------+
期望的结果
按 "history" 成绩对学生进行排序时,我想得到 [ beth (100), mark (90), mike (80) ]
现在假设 mark
在家生病并且错过了 math
考试。
按 "math" 成绩对学生进行排序时,我想得到 [ beth (100), mark (90), mike (null) ]
相反,对于按数学成绩排序 returns [ beth (100), mark (90) ]
。
尝试的解决方案
我知道我可以获取所有结果并重新加入结果,但如果可能的话,我正在寻找一个高效的 ORM 查询集解决方案。
>>> Student.objects.filter(grades__subject__title="history").order_by("grades__value")
<Student: Beth>, <Student: Mark>, <Student: Mike>
>>> Student.objects.filter(grades__subject__title="math").order_by("grades__value")
<Student: Beth>, <Student: Mark>
更新
我尝试按照建议使用联合,但我不确定为什么它会导致相关查找失败:
>>> Student.objects.filter(grades__subject__title="math")\
.union(Student.objects.all())\
.order_by("grades__value")
...
django.core.exceptions.FieldError: Cannot resolve keyword 'value' into field. Choices are: grades, id, name
解决方案
下面使用@schillingt 的解决方案进行验证。
grades = Grade.objects.filter(
student_id=OuterRef('id'), subject__title="math"
)
students = Student.objects.annotate(
subject_grade=Subquery(grades.values('value')[:1])
)
>>> [f"{s.name}: {s.subject_grade}" for s in students.order_by("subject_grade")]
['mike: None', 'mark: 90', 'beth: 100']
>>>
>>> [f"{s.name}: {s.subject_grade}" for s in students.order_by("-subject_grade")]
['beth: 100', 'mark: 90', 'mike: None']
>>>
我会在注释中使用 Subquery expression。
from django.db.models import Subquery, OuterRef
subject = "math"
grades = Grade.objects.filter(
student_id=OuterRef('id'),
subject__title=subject,
).order_by('value')
students = Student.objects.annotate(
annotated_grade=Subquery(grades.values('value')[:1]),
)
print(students.first().annotated_grade) # The first student's highest math grade.
上下文
我正在尝试根据反向外键关系的值对对象列表进行排序。 我有部分解决方案,但使用的过滤器会创建一个内部联接,该联接会删除没有关系的对象。
结果我想用所有对象创建排序的查询集,威胁"null" 反向关系,就好像相关值是空的。
型号
class Student(models.Model):
name = models.CharField(max_length=128)
class Subject(models.Model):
title = models.CharField(max_length=128)
class Grade(models.Model):
student = models.ForeignKey("Student", related_name="grades", on_delete=models.CASCADE)
subject = models.ForeignKey("Subject", related_name="grades", on_delete=models.CASCADE)
value = models.IntegerField()
赛程
+------+------------------------+
| name | subject | grade_value |
+------+----------+-------------+
| beth | math | 100 |
| beth | history | 100 |
| mark | math | 90 |
| mark | history | 90 |
| mike | history | 80 |
+------+----------+-------------+
期望的结果
按 "history" 成绩对学生进行排序时,我想得到 [ beth (100), mark (90), mike (80) ]
现在假设 mark
在家生病并且错过了 math
考试。
按 "math" 成绩对学生进行排序时,我想得到 [ beth (100), mark (90), mike (null) ]
相反,对于按数学成绩排序 returns [ beth (100), mark (90) ]
。
尝试的解决方案
我知道我可以获取所有结果并重新加入结果,但如果可能的话,我正在寻找一个高效的 ORM 查询集解决方案。
>>> Student.objects.filter(grades__subject__title="history").order_by("grades__value")
<Student: Beth>, <Student: Mark>, <Student: Mike>
>>> Student.objects.filter(grades__subject__title="math").order_by("grades__value")
<Student: Beth>, <Student: Mark>
更新
我尝试按照建议使用联合,但我不确定为什么它会导致相关查找失败:
>>> Student.objects.filter(grades__subject__title="math")\
.union(Student.objects.all())\
.order_by("grades__value")
...
django.core.exceptions.FieldError: Cannot resolve keyword 'value' into field. Choices are: grades, id, name
解决方案
下面使用@schillingt 的解决方案进行验证。
grades = Grade.objects.filter(
student_id=OuterRef('id'), subject__title="math"
)
students = Student.objects.annotate(
subject_grade=Subquery(grades.values('value')[:1])
)
>>> [f"{s.name}: {s.subject_grade}" for s in students.order_by("subject_grade")]
['mike: None', 'mark: 90', 'beth: 100']
>>>
>>> [f"{s.name}: {s.subject_grade}" for s in students.order_by("-subject_grade")]
['beth: 100', 'mark: 90', 'mike: None']
>>>
我会在注释中使用 Subquery expression。
from django.db.models import Subquery, OuterRef
subject = "math"
grades = Grade.objects.filter(
student_id=OuterRef('id'),
subject__title=subject,
).order_by('value')
students = Student.objects.annotate(
annotated_grade=Subquery(grades.values('value')[:1]),
)
print(students.first().annotated_grade) # The first student's highest math grade.