在Django中使用窗口函数获取特定用户的排名

-1 投票
1 回答
60 浏览
提问于 2025-04-13 15:27

这是我的查询,它可以给我所有用户的排名:

rank = Wallet.objects.filter(type=0, space_id=space.id).annotate(
            rank=Window(
                expression=DenseRank(),
                order_by=F('point_amount').desc(),
            )
        ).order_by('rank').values("user__id","user__username","rank")

但是我该如何在这个查询中获取特定用户的排名呢?(我不想用Python来处理这个)

我已经尝试过这个:

rank = Wallet.objects.filter(type=0, space_id=space.id, user_id=user.id).annotate(
            rank=Window(
                expression=DenseRank(),
                order_by=F('point_amount').desc(),
            )
        ).order_by('rank').values("user__id","user__username","rank")

但它给我的排名是1,这并不正确

注意:这个原始查询给了我正确的答案,但我不知道如何把它转换成Django的ORM查询

query = f'''
            SELECT
                FIND_IN_SET(
                    `transaction_wallet`.`point_amount`,
                    (
                        SELECT
                        GROUP_CONCAT(`transaction_wallet`.`point_amount` ORDER BY `transaction_wallet`.`point_amount` DESC)
                        FROM `transaction_wallet`
                        WHERE `transaction_wallet`.`type` = 0 AND `transaction_wallet`.`space_id` = {space.id}
                    )
                ) AS `rank`
            FROM
                `transaction_wallet`
            WHERE
                `transaction_wallet`.`type` = 0 AND `transaction_wallet`.`space_id` = {space.id} AND `transaction_wallet`.`user_id` = {self.id};
        '''
    ```

1 个回答

-1

Django 的查询集是懒惰的,也就是说,它们只有在需要的时候才会被计算。这意味着,当你执行查询时,user_id 这个过滤条件会把查询集限制到一个单独的对象,然后才会计算窗口函数。最终的结果是 rank 的值为 1

为了绕过这个问题,你需要先构建一个不带 user_id 过滤条件的查询,让 Django 执行这个查询(包括注解),然后再应用 user_id 过滤条件。

# Step 1: Build the annotated query
rank = (Wallet
        .objects
        .filter(type=0, space_id=space.id)
        .annotate(rank=Window(expression=DenseRank(),
                              order_by=F('point_amount').desc())
        .order_by('rank'))
# Step 2: Evaluate the query
rank.count()

# Step 3: Now apply the filter
ranked_user_wallet = rank.filter(user_id=user.id)

# Now ranked_user_wallet is a queryset of Wallet objects
# of the desired user with the correct rank annotation applied

撰写回答