Эффективное получение первого и последнего экземпляров модели в Django Model с меткой времени, по дням

Предположим, что у вас есть такая модель:

from django import models
from django.contrib.postgres.indexes import BrinIndex


class MyModel(model.Models):
  device_id = models.IntegerField()
  timestamp = models.DateTimeField(auto_now_add=True)
  my_value = models.FloatField()

  class Meta:
    indexes = (BrinIndex(fields=['timestamp']),)

Существует периодический процесс, который создает экземпляр этой модели каждые 2 минуты или около того. Предполагается, что этот процесс будет работать годами, с множеством устройств, поэтому эта таблица будет содержать большое количество записей.

Моя цель - для каждого дня, когда есть записи, получить первую и последнюю записи за этот день.

Пока что я смог придумать следующее:

from django.db.models import Min, Max


results = []
device_id = 1 # Could be other device id, of course, but 1 for illustration's sake

# This will get me a list of dictionaries that have first and last fields 
# with the desired timestamps, but not the field my_value for them.

first_last = MyModel.objects.filter(device_id=device_id).values('timestamp__date')\
.annotate(first=Min('timestamp__date'),last=Max('timestamp__date'))

# So now I have to iterate over that list to get the instances/values
  
for f in first_last:

    first = f['first']
    last = f['last']

    first_value = MyModel.objects.get(device=device, timestmap=first).my_value
    last_value = MyModel.objects.get(device=device, timestamp=last).my_value

    results.append({
      'first': first,
      'last': last,
      'first_value': first_value,
      'last_value': last_value,
    })

# Do something with results[]

Это работает, но занимает много времени (около 50 секунд на моей машине, извлекая первые и последние значения для примерно 450 дней).

Я пробовал другие комбинации annotate(), values(), values_list(), extra() и т.д., но это лучшее, что я смог придумать на данный момент.

Любая помощь или понимание будут оценены по достоинству!

Вы можете воспользоваться преимуществами .distinct(), если вы используете PostgreSQL в качестве СУБД.

first_models = MyModel.objects.order_by('timestamp__date', 'timestamp').distinct('timestamp__date')
last_models = MyModel.objects.order_by('timestamp__date', '-timestamp').distinct('timestamp__date')
first_last = first_models.union(last_models)

# do something with first_last

Необходимо упомянуть еще одну вещь: first_last может исключить дублирование, если для даты существует только одна запись. Это не должно быть проблемой для вас, но если это произойдет, вы можете выполнить итерации first_models и last_models отдельно.

Вернуться на верх