Django:计算同一模型中行上的字段之间的差异

Nel*_*son 3 django django-queryset

我有一个模型,代表从运动开始的持续时间:

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()
Run Code Online (Sandbox Code Playgroud)

我想计算每个围兜的两个计时点之间的持续时间。结果应该是一个查询集,每个号码布一行。我认为如果数据获取和持续时间计算可以在一个数据库查询中完成,那将是最有效的。这可以做到吗?如何做到?

为了澄清目标,我现在这样做(删除了异常处理):

    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, })        
Run Code Online (Sandbox Code Playgroud)

我想避免多次访问数据库,并且我更希望结果是一个 QuerySet,因为我希望能够执行其他操作,例如排序。

Wil*_*sem 5

及更高版本中,我们可以使用.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)
Run Code Online (Sandbox Code Playgroud)

我们首先引入两个变量:

  1. t2将包含与相关的最小time对象(可能只有一个);和 Timetiming_pointto_point
  2. t1将包含具有的相关对象time的最小者。 Timetiming_pointfrom_point

然后我们对和dtime之间的差异进行注释。t2t1

由于这仍然是 的一部分QuerySet,我们甚至可以Bib在 等上订购 s 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
Run Code Online (Sandbox Code Playgroud)

实际上,2和分别是和的主键。1to_pointfrom_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)
Run Code Online (Sandbox Code Playgroud)

这将导致一个如下所示的查询:

SELECT bib.*,
       (MIN(CASE WHEN time.timing_point_id = 2 THEN time.time ELSE NULL END) -
        MIN(CASE WHEN time.timing_point_id = 2 THEN time.time ELSE NULL END)) AS dtime
FROM bib LEFT OUTER JOIN time ON bib.id = time.bib_id
WHERE time.timing_point_id IN (1, 2)
GROUP BY bib.id
Run Code Online (Sandbox Code Playgroud)