Запрос Django, который получает самые последние объекты из разных категорий

У меня две модели A а также B, Все B объекты имеют внешний ключ к A объект. Учитывая набор A объекты, есть ли в любом случае использовать ORM, чтобы получить набор B объекты, содержащие самый последний объект, созданный для каждого A объект

Вот упрощенный пример:

Class Bakery(models.Model):
    town = models.CharField()

Class Cake(models.Model):
    bakery = models.ForeignKey(Bakery)
    baked_at = models.DateTimeField()

Поэтому я ищу запрос, который возвращает самый последний торт, выпеченный в каждой пекарне в Anytown, США.

6 ответов

Решение

Насколько я знаю, в Django ORM нет одношагового способа сделать это.

Но вы можете разделить его на два запроса:

bakeries = Bakery.objects.annotate(
    hottest_cake_baked_at=Max('cake__baked_at')
) 
hottest_cakes = Cake.objects.filter(
    baked_at__in=[b.hottest_cake_baked_at for b in bakeries]
)

Если идентификаторы тортов развиваются вместе с временными метками bake_at, вы можете упростить и устранить неоднозначность вышеуказанного кода (в случае, если два торта прибывают одновременно, вы можете получить оба из них):

hottest_cake_ids = Bakery.objects.annotate(
    hottest_cake_id=Max('cake__id')
).values_list('hottest_cak‌​e_id', flat=True)

hottest_cakes = Cake.objects.filter(id__in=hottest_cake_ids)

Кстати, это относится к Дэниелу Роузману, который однажды ответил на мой похожий вопрос:

http://groups.google.pl/group/django-users/browse_thread/thread/3b3cd4cbad478d34/3e4c87f336696054?hl=pl&q=

Если описанный выше метод слишком медленный, то я знаю и второй метод - вы можете написать собственный SQL, производящий только те Cakes, которые наиболее популярны в соответствующих пекарнях, определить его как базу данных VIEW, а затем написать для нее неуправляемую модель Django. Это также упомянуто в вышеупомянутой ветке django-users. Прямая ссылка на оригинальную концепцию здесь:

http://web.archive.org/web/20130203180037/http://wolfram.kriesing.de/blog/index.php/2007/django-nice-and-critical-article

Надеюсь это поможет.

Начиная с Django 1.11 и благодаря Subquery и OuterRef, и мы, наконец, можем построить latest-per-group запрос с использованием ORM,

hottest_cakes = Cake.objects.filter(
    baked_at=Subquery(
        (Cake.objects
            .filter(bakery=OuterRef('bakery'))
            .values('bakery')
            .annotate(last_bake=Max('baked_at'))
            .values('last_bake')[:1]
        )
    )
)

#BONUS, we can now use this for prefetch_related()
bakeries = Bakery.objects.all().prefetch_related(
    Prefetch('cake_set',
        queryset=hottest_cakes,
        to_attr='hottest_cakes'
    )
)

#usage
for bakery in bakeries:
    print 'Bakery %s has %s hottest_cakes' % (bakery, len(bakery.hottest_cakes))

Если вы используете PostGreSQL, вы можете использовать интерфейс Django для DISTINCT ON:

recent_cakes = Cake.objects.order_by('bakery__id', '-baked_at').distinct('bakery__id')

Как говорят в документах, вы должны order by те же поля, что вы distinct on, Как указал Саймон ниже, если вы хотите выполнить дополнительную сортировку, вам придется делать это в пространстве Python.

Это должно сделать работу:

from django.db.models import Max
Bakery.objects.annotate(Max('cake__baked_at'))

Я боролся с подобной проблемой и, наконец, пришел к следующему решению. Это не зависит от order_by а также distinct поэтому может быть отсортирован по желанию на стороне базы данных, а также может использоваться как вложенный запрос для фильтрации. Я также считаю, что эта реализация не зависит от двигателя БД, потому что она основана на стандартном SQL HAVING пункт. Единственным недостатком является то, что он будет возвращать несколько самых горячих пирогов в пекарне, если они выпекаются в этой пекарне в одно и то же время.

from django.db.models import Max, F

Cake.objects.annotate(
    # annotate with MAX "baked_at" over all cakes in bakery
    latest_baketime_in_bakery=Max('bakery__cake_set__baked_at')
    # compare this cake "baked_at" with annotated latest in bakery
).filter(latest_baketime_in_bakery__eq=F('baked_at'))
Cake.objects.filter(bakery__town="Anytown").order_by("-created_at")[:1]

Я не построил модели с моей стороны, но теоретически это должно работать. Сломано:

  • Cake.objects.filter(bakery__town="Anytown") Следует вернуть любые пирожные, которые принадлежат "Anytown", при условии, что страна не является частью строки. Двойное подчеркивание между bakery а также town позволяют нам получить доступ к town собственностью bakery,
  • .order_by("-created_at") упорядочит результаты по дате их создания, самой последней первой (обратите внимание на - (минус) войти "-created_at", Без знака минус они будут заказываться от самых старых до самых последних.
  • [:1] в конце будет возвращен только 1-й элемент в списке, который будет возвращен (который будет списком тортов из Anytown, отсортированным по последнему из первых).

Примечание: этот ответ для Джанго 1.11. Этот ответ был изменен из запросов, показанных здесь в Django 1.11 Docs.

Решение @Tomasz Zieliński, приведенное выше, решило вашу проблему, но не решило мою, потому что мне все еще нужно отфильтровать торт. Итак, вот мое решение

from django.db.models import Q, Max

hottest_yellow_round_cake = Max('cake__baked_at', filter=Q(cake__color='yellow', cake__shape='round'))

bakeries = Bakery.objects.filter(town='Chicago').annotate(
    hottest_cake_baked_at=hottest_yellow_round_cake
)

hottest_cakes = Cake.objects.filter(
    baked_at__in=[b.hottest_cake_baked_at for b in bakeries]
)

При таком подходе вы также можете реализовать другие вещи, такие как фильтр, сортировка, разбивка на страницы для тортов.

Другие вопросы по тегам