Django查询从不同类别获取最新对象

时间:2010-01-15 20:21:02

标签: python django django-queryset greatest-n-per-group

我有两个模型AB。所有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每家面包店最新蛋糕的查询。

7 个答案:

答案 0 :(得分:29)

据我所知,在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_cak‌​e_id', flat=True)

hottest_cakes = Cake.objects.filter(id__in=hottest_cake_ids)
对此,BTW学分归丹尼尔罗斯曼所说,曾经回答了我的类似问题:

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

希望这有帮助。

答案 1 :(得分:20)

Django 1.11开始,感谢SubqueryOuterRef,我们最终可以使用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))

答案 2 :(得分:17)

如果您正好使用PostGreSQL,可以使用Django's interface to DISTINCT ON

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

正如the docs所述,您必须order by与您distinct on相同的字段。正如Simon在下面指出的那样,如果你想进行额外的排序,你必须在Python空间中进行排序。

答案 3 :(得分:5)

这应该做的工作:

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

答案 4 :(得分:3)

我正在与类似的问题作斗争,最后得到以下解决方案。它不依赖于order_bydistinct,因此可以根据需要在db-side上进行排序,也可以用作嵌套查询进行过滤。我也相信这个实现是独立于数据库引擎的,因为它基于标准的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'))

答案 5 :(得分:0)

Cake.objects.filter(bakery__town="Anytown").order_by("-created_at")[:1]

我还没有在我的最终建立模型,但理论上这应该有效。细分:

  • Cake.objects.filter(bakery__town="Anytown")应该归还任何属于" Anytown"的蛋糕,假设该国家不属于该字符串。 bakerytown之间的双下划线允许我们访问town的{​​{1}}属性。
  • bakery会按照创建日期对结果进行排序,最近一次(请注意.order_by("-created_at")中的-(减号)符号。如果没有减号,他们会在d按最旧到最近的顺序排列。
  • 最后的
  • "-created_at"将仅返回列表中返回的第一项(这将是来自Anytown的蛋糕列表,按最近的排序)。

注意:这个答案适用于Django 1.11。 此答案根据here in Django 1.11 Docs显示的查询进行了修改。

答案 6 :(得分:0)

上面的

@TomaszZieliński解决方案确实解决了您的问题,但并没有解决我的问题,因为我仍然需要过滤掉Cake。所以这是我的解决方法

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

通过这种方法,您还可以实现其他功能,例如“过滤器”,“订购”,“蛋糕分页”