Django — получение объектов с последней даты для каждой группы — PersonPhoto

#python #python-3.x #django #django-models #django-queryset

Вопрос:

Моя база данных содержит паспортные изображения разных людей. Что-то вроде:

 class Person(models.Model):
    pass

class PersonPhoto(models.Model):
    date_captured = models.DateField()
    person = models.ForeignKey(Person, null=False)
 

Я хочу извлечь для каждого человека все изображения с последней даты, когда он был сфотографирован.
Итак, если у человека А есть фотографии с августа 5, 5, 9, 11, 11, и у человека Б есть изображения с августа 7, 9, 13, 13, 19, 19 затем я хочу получить оба изображения от 11 августа для человека А и оба изображения от 19 августа для человека Б.

То, как я сейчас это делаю, это что-то вроде:

 specific_dates_queryset = Q()
for photo in PersonPhoto.objects.all().values('person_id').annotate(max_date=Max('date_captured')):
    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}")
 

Идея состоит в том, чтобы сначала найти последнюю дату фотографии для каждого человека, а затем в новом запросе получить эти изображения для этих людей с этих дат.

Существует ли более простое решение, которое делает все в базе данных и позволяет избежать избыточных запросов и извлечения данных?

Ответ №1:

Простой способ сделать это в одном запросе-снабдить каждую фотографию последней датой для соответствующего лица, а затем отфильтровать по аннотации. Это должно вернуть все желаемое PersonPhoto в наборе запросов

 from django.db.models import Max, F

PersonPhoto.objects.annotate(
   latest=Max('person__personphoto__date_captured')
).filter(
    date_captured=F('latest')
)
 

Я не уверен, насколько это будет эффективно из-за аннотации, это может зависеть от используемой вами базы данных и характера ваших данных

Ответ №2:

Вы можете предварительно выбрать все связанные фотографии человека и отфильтровать их на основе последней даты, сделанной этим человеком:

 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
 

Это в общей сложности выполнит два запроса: один для списка людей, а другой для получения всех отфильтрованных связанных фотографий каждого человека.