Django - получение объектов по последней дате для каждой группы - PersonPhoto
Моя БД содержит изображения паспортов разных людей. Что-то вроде:
class Person(models.Model):
pass
class PersonPhoto(models.Model):
date_captured = models.DateField()
person = models.ForeignKey(Person, null=False)
Я хочу извлечь для каждого человека все изображения с последней даты, когда он был сфотографирован. Так, если у человека A есть фотографии от 5, 5, 9, 11, 11 августа, а у человека B есть изображения от 7, 9, 13, 13, 19, 19 августа, то я хочу извлечь оба изображения от 11 августа для человека A, и оба изображения от 19 августа для человека B.
В настоящее время я делаю это следующим образом:
specific_dates_queryset = Q()
for photo in PersonPhoto.objects.all().annotate(max_date=Max('date_captured')).values('person_id'):
specific_dates_queryset |= Q(person_id=photo["person_id"], date_captured=photo["max_date"])
for photo in PersonPhoto.objects.filter(specific_dates_queryset).order_by("person_id"):
print(f"image for person {photo.person_id}, of date {photo.date_captured}")
Идея состоит в том, чтобы сначала найти последнюю дату фотографии для каждого человека, а затем в новом запросе получить эти изображения для этих людей из этих дат.
Есть ли более простое решение, которое делает все внутри БД и позволяет избежать избыточных запросов и получения данных?
Вы можете получить все связанные фотографии человека и отфильтровать их на основе последней даты, указанной на фотографии, следующим образом:
from django.db.models import F, Max, Prefetch
person_qs = Person.objects.annotate(
latest_photo_date=Max('personphoto__date_captured')
).prefetch_related(
Prefetch(
'personphoto_set',
queryset=PersonPhoto.objects.annotate(
person_latest_photo_captured=Max('person__personphoto__date_captured')
).filter(
date_captured=F('person_latest_photo_captured')
),
to_attr='latest_photos',
)
)
Все последние экземпляры PersonPhoto
будут доступны в виде списка в атрибуте latest_photos
экземпляра Person
, так что вы можете получить к ним доступ следующим образом:
for person in person_qs:
print(f'Latest images for {person.name} taken on {person.latest_photo_date}:')
for photo in person.latest_photos:
print(f'Photo ID: {photo.id} - Captured at: {photo.date_captured}')
print()
Выход:
Latest images for B taken on 2021-08-19:
Photo ID: 10 - Captured at: 2021-08-19
Photo ID: 11 - Captured at: 2021-08-19
Latest images for A taken on 2021-08-11:
Photo ID: 5 - Captured at: 2021-08-11
Photo ID: 4 - Captured at: 2021-08-11
Всего будет сделано два запроса, один для списка персон, а другой для получения всех отфильтрованных связанных фотографий каждой персоны.
Простой способ сделать это в одном запросе - аннотировать каждую фотографию последней датой для связанного с ней человека, а затем отфильтровать по аннотации. Это должно вернуть все желаемые PersonPhoto
в наборе запросов
from django.db.models import Max, F
PersonPhoto.objects.annotate(
latest=Max('person__personphoto__date_captured')
).filter(
date_captured=F('latest')
)
Я не уверен, насколько это будет эффективно благодаря аннотации, это может зависеть от используемой вами БД и характера ваших данных