我有两个模型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每家面包店最新蛋糕的查询。
答案 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_cake_id', flat=True)
hottest_cakes = Cake.objects.filter(id__in=hottest_cake_ids)
对此,BTW学分归丹尼尔罗斯曼所说,曾经回答了我的类似问题:
如果上面的方法太慢,那么我也知道第二种方法 - 您可以编写自定义SQL,仅生成相关面包店中最热门的Cakes,将其定义为数据库VIEW,然后为其编写非托管Django模型。它也在上面的django-users线程中提到过。这里有与原始概念的直接链接:
希望这有帮助。
答案 1 :(得分:20)
从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))
答案 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_by
和distinct
,因此可以根据需要在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"的蛋糕,假设该国家不属于该字符串。 bakery
和town
之间的双下划线允许我们访问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]
)
通过这种方法,您还可以实现其他功能,例如“过滤器”,“订购”,“蛋糕分页”