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


78

我有两个模型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的每个面包店中烘焙的最新蛋糕。


6
我也很乐意看到它:-)
gruszczy

Answers:


35

据我所知,在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为此功劳归功于Daniel Roseman,他曾经回答过我的类似问题:

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

如果上述方法太慢,那么我也知道第二种方法-您可以编写仅生成在相关Bakeries中最热门的Cake的自定义SQL,将其定义为数据库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

希望这可以帮助。


我可能会考虑您建议的第二组查询。谢谢。
Zach

如果您在第一个查询中使用了value_list,则效率会更高: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)
dbn

另外,如果您碰巧正在使用PostGreSQL,则有一个一步的解决方案。
dbn 2015年

2
第一个解决方案是否会造成一个问题,即一个日期的最新日期早于另一个日期的最新日期,但存在于另一个日期中呢?A = [1,2,3],B = [1,2]。最新的= 3,B =最新2.第一个查询似乎得到A的2和3,以及B的2
kaungst

1
Django 1.11现在开始,这是一个单向步骤。检查我的答案。
多德

32

从开始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))

尽管我的用例略有不同,但这种方法效果很好。我喜欢这种方法的原因是:1)将结果查询集保留在目标模型实例中; 2)不排除没有相关数据的模型实例(在问题中是没有面包店的模型实例)烘烤任何东西)。
Supra621

你是最聪明的人
扬州

19

如果您碰巧正在使用PostGreSQL,则可以使用Django的界面DISTINCT ON

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

文档所述,您必须order by与相同的字段distinct on。正如Simon在下面指出的那样,如果要进行其他排序,则必须在Python空间中进行。


喜欢这种方法-谢谢。只是做了关于最终订购的小修正。根据QS的总大小,这可能比接受的答案更好或更糟。就我而言:更好:)
Simon Steinberger

我认为这是对代码的不必要的复杂化,并且超出了答案范围。我将假设人们可以弄清楚如何对结果数据进行排序。
dbn 2015年

Max经常遇到类似的问题,尝试对它们进行批注和过滤,但是由于django Optimizer删除了order_by(将result用作过滤器子查询时或将聚合用作ex时.count())之后,由于sql不正确,它们最终在db端失败。此解决方案在获取数据时不会破坏所有事情,recent_cakes.count()并且在执行操作时不会引发错误Cake.objects.filter(pk__in=recent_cackes).filter(other_conditions),但是最新示例返回了每个面包店都满足other_condition(不是最热的!)的随机蛋糕,因为djangoorder_by从子查询中删除了:(
Ivan Klass,

是的,出于这个原因,我认为如果您不使用postGreSQL,Tomasz Zielinski的答案就是正确的选择。
dbn 2015年

5

这应该可以完成以下工作:

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

5
我尚未进行测试,但是看起来它将注释每个面包店最近烘烤蛋糕的时间。我正在寻找实际的蛋糕对象。我会误解您的答案吗?
扎克2010年

你是对的。我忘记了为托马斯(Tomasz)发布的先前答案:-)
丹尼尔·罗斯曼

1
我相信这仅在按ID和日期对蛋糕排序相同的顺序下才有效。在一般情况下,主键序列与日期字段所定义的时间顺序不对应,这将不起作用。
德米特里

3

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

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

我还没有建立模型,但是从理论上讲这应该可行。细分:

  • Cake.objects.filter(bakery__town="Anytown")假设该国家/地区不属于字符串,则应返回属于“ Anytown”的所有蛋糕。之间的双下划线bakerytown使我们可以访问的town属性bakery
  • .order_by("-created_at")将按照其创建日期对结果进行排序,最近的是第一个(请注意-登录(减号)"-created_at"。如果没有减号,则按照从最早到最新的顺序对其进行排序。
  • [:1] 最后,将仅返回列表中的第一个项目(该列表将是Anytown的蛋糕列表,以最新的优先顺序排序)。

注意:此答案适用于Django 1.11。 此答案根据Django 1.11 Docs中显示的查询修改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.