Джанго: Рассчитать разницу между полями в строках из той же модели - PullRequest
3 голосов
/ 14 мая 2019

У меня есть модель, которая представляет продолжительность от начала пробега в спорте:

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, так как я хочу иметь возможность делать дополнительные вещи, такие как заказ.

1 Ответ

4 голосов
/ 14 мая 2019

В и выше, мы можем использовать .annotate(..) с агрегатом, таким как Min, и условием filter=... для этого агрегата, например:

from django.db.models import <b>F, Min, Q</b>

t2=Min('time__time', filter=Q(<b>time__timing_point=to_point</b>))
t1=Min('time__time', filter=Q(<b>time__timing_point=from_point</b>))

Bib.objects.annotate(<b>dtime=t2-t1</b>)

Сначала введем две переменные:

  1. t2 будет содержать наименьший time для связанного Time объекта с timing_point to_point (вероятно, есть только один); и
  2. t1 будет содержать наименьший time для связанного Time объекта с timing_point from_point.

Затем делаем аннотацию dtime с разницей между t2 и t1.

Поскольку это все еще часть 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

С 2 и 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(
    <b>time__timing_point__in=[from_point, to_point]</b>
).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
<b>WHERE time.timing_point_id IN (1, 2)</b>
GROUP BY bib.id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...