【问题标题】:Django: Calculate difference between fields on rows from same modelDjango:计算来自同一模型的行上的字段之间的差异
【发布时间】:2019-05-13 21:19:01
【问题描述】:

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

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,因为我希望能够执行其他操作,例如排序。

【问题讨论】:

  • 你使用的是哪个 Django 版本?
  • @WillemVanOnsem 我正在使用 Django 2.2。我会尽快尝试您建议的解决方案,但它看起来很有希望。非常感谢。

标签: django django-queryset


【解决方案1】:

及更高版本中,我们可以将.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)

我们先引入两个变量:

  1. t2 将包含最小的time 用于相关 Time 对象与timing_point to_point(可能只有一个);和
  2. t1 将包含最小的time 用于相关 Time 对象与timing_point from_point

然后我们用t2t1 之间的区别来注释dtime

由于这仍然是QuerySet 的一部分,我们甚至可以在dtime 上订购Bibs,等等。

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

21 实际上分别是 to_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)

这将导致如下查询:

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

【讨论】:

  • 现在是一个可靠的答案。
  • @WillemVanOnsem 经过测试,效果很好。谢谢!
猜你喜欢
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 2018-09-14
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多