Django: Получение последнего N количества записей для каждой группы

Допустим, у меня есть следующая модель Django:

class Team(models.Model):
    name = models.CharField(max_length=255, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

Я хочу написать запрос для получения последнего N количества записей по названию команды.

Если N=1, то запрос очень прост (предполагая, что я использую postgres, потому что это единственная БД, которая поддерживает distinct(*fields)):

Team.objects.order_by("name", "-created_at").distinct("name")

Если N больше 1 (допустим, 3), то все становится сложнее. Как я могу написать этот запрос в Django?

Я предполагаю, что вы будете получать N из запроса get или чего-то еще, но пока у вас есть число, вы можете попробовать ограничить ваш набор запросов:

Team.objects.order_by("name", "-created_at").distinct("name")[:3] # for N = 3 

Не уверен, как вы можете получить дубликаты имен на команду, поскольку у вас есть unique=True. Но если вы планируете убрать это для поддержки неуникальных имен, вы можете использовать подзапросы, например, такой:

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

Team.objects.filter(
    id__in=top_3_per_team_name.values("id")
)

Хотя это может быть немного медленно, поэтому убедитесь, что у вас настроены индексы.

Также следует отметить, что в идеале это можно решить с помощью 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

С учетом вышесказанного вы получаете:

NotSupportedError: Window is disallowed in the filter clause.

Но есть план по поддержке этого на Django 4.2, так что теоретически вышеописанное должно работать после его выхода.

Вернуться на верх