我有一个模型,它代表一项运动开始前的持续时间:
class Time(models.Model):
# A participator of a race
bib = models.ForeignKey(Bib, on_delete=models.CASCADE)
# A timing point. Could be one at 0km, 5km and at the goal
timing_point = models.ForeignKey(TimingPoint, on_delete=models.CASCADE)
# The duration from start
time = models.FloatField()
我想为每个围嘴计算两个计时点之间的持续时间。结果应该是每个围兜有一行的QuerySet。我认为如果可以在一个数据库查询中完成数据获取和持续时间计算,那将是最有效的。能做到这一点吗?
要弄清目标,这就是我现在要做的(删除了异常处理):
times = []
for bib in bibs:
from_time = Time.objects.get(timing_point=from_point, bib=bib)
to_time = Time.objects.get(timing_point=to_point, bib=bib)
times.append({'bib': bib, 'time': to_time.time - from_time.time, })
我想避免多次访问数据库,因此我希望结果是否为QuerySet,因为我希望能够执行其他操作,例如订购。
答案 0 :(得分:4)
在django-2.0及更高版本中,我们可以将.annotate(..)
与Min
之类的聚合一起使用,并在该聚合上使用filter=...
条件,例如:
from django.db.models import F, Min, Q
t2=Min('time__time', filter=Q(time__timing_point=to_point))
t1=Min('time__time', filter=Q(time__timing_point=from_point))
Bib.objects.annotate(dtime=t2-t1)
我们首先介绍两个变量:
t2
的对象,time
将包含最小的Time
,timing_point
与to_point
(可能只有一);和t1
的对象,time
将包含最小的Time
,其中timing_point
是from_point
。然后我们用dtime
和t2
之间的区别进行注释t1
。
由于这仍然是QuerySet
的一部分,因此我们甚至可以在Bib
上订购dtime
,等等。
Django会将其转换为如下查询:
SELECT bib.*,
(MIN(CASE WHEN time.timing_point_id = 2 THEN time.time ELSE NULL END) -
MIN(CASE WHEN time.timing_point_id = 1 THEN time.time ELSE NULL END)) AS dtime
FROM bib
LEFT OUTER JOIN time ON bib.id = time.bib_id GROUP BY bib.id
实际上,2
和1
的主键分别是{{1}}和to_point
。
如果您也过滤from_point
模型,它可能会进一步提高查询效率:
Timing
这将导致查询如下:
from django.db.models import F, Min, Q
t2=Min('time__time', filter=Q(time__timing_point=to_point))
t1=Min('time__time', filter=Q(time__timing_point=from_point))
Bib.objects.filter(
time__timing_point__in=[from_point, to_point]
).annotate(dtime=t2-t1)