Возможно ли использовать queryset в предложении FROM

У меня есть модель для сбора очков пользователя:

class Rating(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='rating')
    points = models.IntegerField()

В этой модели у каждого пользователя может быть несколько записей. Мне нужно вычислить ранг каждого пользователя по сумме набранных баллов. Для листинга это просто:

Rating.objects.values('user__username').annotate(
    total_points=Sum('points')
).order_by('-total_points')

Но как получить ранг для одного пользователя по его user_id? Я добавил аннотацию с номерами строк:

Rating.objects.values('user__username').annotate(
    total_points=Sum('points')
).annotate(
    rank=Window(
        expression=RowNumber(),
        order_by=[F('total_points').desc()]
    )
)

он действительно добавил правильные номера ранжирования, но когда я пытаюсь получить одного пользователя по user_id, он возвращает строку с рангом=1. Это происходит потому, что условие фильтрации переходит в пункт WHERE и там есть единственный ряд с номером 1. Я имею в виду следующее:

Rating.objects.values('user__username').annotate(
    total_points=Sum('points')
).annotate(
    rank=Window(
        expression=RowNumber(),
        order_by=[F('total_points').desc()]
    )
).filter(user_id=1)

Я получил SQL запрос этого набора запросов (qs.query) как

SELECT ... FROM rating_rating WHERE ...

и вставил его в другой SQL запрос как "rank_table" и добавил условие во внешний пункт WHERE:

SELECT * FROM (SELECT ... FROM rating_rating WHERE ...) AS rank_table WHERE user_id = 1;

и выполняется в консоли MySQL. И это работает именно так, как мне нужно. Вопрос в том, как реализовать то же самое, используя Django ORM?

У меня есть одно решение, чтобы получить то, что мне нужно. Я могу добавить еще одно поле, чтобы отметить записи как "правильные" или "неправильные", отсортировать результат по этому полю и затем получить первую строку:

qs.annotate(
    required_user=Case(
        When(user_id=1, then=1),
        default=0,
        output_field=IntegerField(),
    )
).order_by('-required_user').first()

Это работает. Но SELECT внутри другого SELECT кажется более элегантным, и я хотел бы знать, возможно ли это в Django.

Как-то недавно кто-то спросил о фильтрации на оконных функциях. Хотя то, что вы хотите, в основном subquery (select в select), использование аннотации с оконной функцией не поддерживается : https://code.djangoproject.com/ticket/28333, потому что аннотированные поля будут внутри подзапроса :'(. Можно предоставить сырой sql с помощью query_with_params, но это не очень элегантно.

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