Django Query, которые получают самые последние объекты из разных категорий
У меня две модели 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, США.
Ответы
Ответ 1
Насколько я знаю, в 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])
Если id тортов прогрессирует вместе с отметками времени bake_at, вы можете упростить и повторно устранить вышеуказанный код (в случае, если два пирога поступают одновременно, вы можете получить их оба):
hottest_cake_ids = Bakery.objects.annotate(hottest_cake_id=Max('cake__id')).values_list('hottest_cake_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
Надеюсь, что это поможет.
Ответ 2
Если вы используете PostGreSQL, вы можете использовать интерфейс Django для DISTINCT ON:
recent_cakes = Cake.objects.order_by('bakery__id', '-baked_at').distinct('bakery__id')
Как документы говорят, вы должны order by
те же поля, что и distinct on
. Как Саймон указал ниже, если вы хотите выполнить дополнительную сортировку, вам придется сделать это в пространстве Python.
Ответ 3
Это должно выполнить задание:
from django.db.models import Max
Bakery.objects.annotate(Max('cake__baked_at'))
Ответ 4
Начиная с 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))
Ответ 5
Я боролся с подобной проблемой и, наконец, пришел к следующему решению. Он не полагается на order_by
и distinct
, поэтому их можно сортировать по желанию на стороне db, а также использовать в качестве вложенного запроса для фильтрации. Я также считаю, что эта реализация независима от db, потому что она основана на стандартном 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'))
Ответ 6
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]
в конце будет возвращен только первый элемент в возвращаемом списке (который будет списком тортов из Anytown, отсортированным по последним первым).
Примечание. Этот ответ предназначен для Django 1.11.
Этот ответ изменен из запросов, показанных здесь, в Django 1.11 Docs.