Запрос Django, который получает самые последние объекты из разных категорий - PullRequest
65 голосов
/ 15 января 2010

У меня есть две модели 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 ]

29 голосов
/ 16 января 2010

Насколько я знаю, в 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#comment-48425

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

21 голосов
/ 12 мая 2017

Начиная с Django 1.11 и благодаря подзапросам и 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))
17 голосов
/ 21 ноября 2013

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

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

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

5 голосов
/ 15 января 2010

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

from django.db.models import Max
Bakery.objects.annotate(Max('cake__baked_at'))
3 голосов
/ 01 апреля 2015

Я боролся с подобной проблемой и, наконец, пришел к следующему решению. Он не зависит от 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'))
0 голосов
/ 25 мая 2017
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, отсортированным по последнему слову)показано здесь, в Django 1,11 Документы .
...