Django:获取每组最新的N条记录

Joh*_*etz 5 django django-models django-queryset

假设我有以下 Django 模型:

class Team(models.Model):
    name = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
Run Code Online (Sandbox Code Playgroud)

我想编写一个查询来获取每个团队名称的最新 N 条记录。

如果 N=1,查询非常简单(假设我使用 postgres,因为它是唯一支持的数据库distinct(*fields)):

Team.objects.order_by("name", "-created_at").distinct("name")
Run Code Online (Sandbox Code Playgroud)

如果 N 大于 1(假设为 3),那么事情就会变得棘手。我怎样才能在 Django 中编写这个查询?

bdb*_*dbd 5

不确定如何获得每个团队的重复名称,因为您有unique=True. 但如果您打算删除它以支持非唯一名称,则可以使用如下子查询:

top_3_per_team_name = Team.objects.filter(
    name=OuterRef("name")
).order_by("-created_at")[:3]

Team.objects.filter(
    id__in=Subquery(top_3_per_team_name.values("id"))
)
Run Code Online (Sandbox Code Playgroud)

尽管这可能有点慢,所以请确保您已设置索引。

另请注意,理想情况下这可以通过使用Window..[Django-doc]函数使用DenseRank..[Django-doc]来解决,但不幸的是最新的 django 版本无法在 Windows 上过滤:

from django.db.models import F
from django.db.models.expressions import Window
from django.db.models.functions import DenseRank

Team.objects.annotate(
    rank=Window(
        expression=DenseRank(),
        partition_by=[F('name'),],
        order_by=F('created_at').desc()
    ),
).filter(rank__in=range(1,4)) # 4 is N + 1 if N = 3
Run Code Online (Sandbox Code Playgroud)

有了上面的内容,你就得到:

NotSupportedError: Window is disallowed in the filter clause.
Run Code Online (Sandbox Code Playgroud)

但有计划在 Django 4.2 上支持这一点,因此理论上,一旦发布,上述内容就应该可以工作。