81

我有两个模型AB. 所有B对象都有一个对象的外键A。给定一组A对象,无论如何都可以使用 ORM 来获取一组B对象,其中包含为每个对象创建的最新A对象。

这是一个简化的示例:

class Bakery(models.Model):
    town = models.CharField(max_length=255)

class Cake(models.Model):
    bakery = models.ForeignKey(Bakery, on_delete=models.CASCADE)
    baked_at = models.DateTimeField()

因此,我正在寻找一个查询,该查询返回在美国 Anytown 的每个面包店中烘焙的最新蛋糕。

4

7 回答 7

39

SubqueryDjango 1.11OuterRef 开始,我们终于可以使用.latest-per-groupORM

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))
于 2017-05-11T22:15:21.213 回答
36

As far as I know, there is no one-step way of doing this in Django ORM, but you can split it into two queries:

from django.db.models import Max

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]
)

If id's of cakes are progressing along with bake_at timestamps, you can simplify and disambiguate the above code (in case two cakes arrives at the same time you can get both of them):

from django.db.models import Max

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)

BTW credits for this goes to Daniel Roseman, who once answered similar question of mine:

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

If the above method is too slow, then I know also second method - you can write custom SQL producing only those Cakes, that are hottest in relevant Bakeries, define it as database VIEW, and then write unmanaged Django model for it. It's also mentioned in the above django-users thread. Direct link to the original concept is here:

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

Hope this helps.

于 2010-01-16T08:12:43.717 回答
23

如果你碰巧使用 PostGreSQL,你可以使用Django 的 DISTINCT ON 接口

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

正如文档所说,您必须order by使用与您相同的字段distinct on。正如西蒙在下面指出的那样,如果您想进行额外的排序,则必须在 Python 空间中进行。

于 2013-11-21T18:35:42.143 回答
5

This should do the job:

from django.db.models import Max
Bakery.objects.annotate(Max('cake__baked_at'))
于 2010-01-15T20:34:44.510 回答
4

我正在与类似的问题作斗争,最终得出以下解决方案。它不依赖order_bydistinct因此可以在数据库端根据需要进行排序,也可以用作过滤的嵌套查询。我也相信这个实现是独立于数据库引擎的,因为它基于标准的 sqlHAVING子句。唯一的缺点是每个面包店会返回多个最热的蛋糕,如果它们是在同一时间在面包店烘烤的话。

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'))
于 2015-04-01T12:07:08.333 回答
0
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中显示的查询修改而来的。

于 2017-05-25T09:12:48.787 回答
0

上面的@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]
)

使用这种方法,您还可以实现其他功能,例如过滤器、排序、蛋糕分页

于 2020-11-19T08:07:25.670 回答