如何在Django中使用窗口函数获取特定用户的正确排名
如何在Django中使用窗口函数获取特定用户的正确排名
我明白你的问题啦!你之前的写法之所以得到rank=1,是因为窗口函数的计算范围被提前过滤后的数据集限制了——当你先filter特定用户,窗口函数只能看到这一条数据,自然rank就是1。下面给你两种正确的解决思路:
方法一:先计算全局排名,再筛选特定用户(推荐)
这是最符合Django ORM最佳实践的方式,先对所有符合条件的用户计算全局排名,再过滤出目标用户的记录,这样得到的rank就是正确的全局排名:
from django.db.models import Window, DenseRank, F # 先计算所有符合条件的钱包记录的全局排名,再筛选目标用户 user_rank = Wallet.objects.filter(type=0, space_id=space.id) \ .annotate( rank=Window( expression=DenseRank(), order_by=F('point_amount').desc() # 这里不需要partition_by,因为我们要的是全局排名 ) ) \ .filter(user_id=user.id) \ .values("user__id", "user__username", "rank") \ .first()
这个逻辑很清晰:先给所有type=0且属于目标space的Wallet记录计算出正确的DenseRank,之后再从中挑出你要的用户,这样拿到的rank就是该用户在全局中的真实排名。
方法二:对应你原生SQL的ORM写法
如果你想实现和你给出的原生SQL逻辑一致的ORM查询,可以用Subquery和StringAgg来模拟GROUP_CONCAT,再调用FIND_IN_SET函数:
from django.db.models import Subquery, StringAgg, F, Func # 先构建子查询,获取按point_amount降序拼接的字符串 ranked_points_subquery = Wallet.objects.filter(type=0, space_id=space.id) \ .order_by('-point_amount') \ .annotate(ranked_points=StringAgg(F('point_amount'), delimiter=',')) \ .values('ranked_points') # 再查询目标用户,用FIND_IN_SET计算排名 user_rank = Wallet.objects.filter(type=0, space_id=space.id, user_id=user.id) \ .annotate( ranked_points=Subquery(ranked_points_subquery) ) \ .annotate( rank=Func(F('point_amount'), F('ranked_points'), function='FIND_IN_SET') ) \ .values("user__id", "user__username", "rank") \ .first()
不过这种写法相对复杂,不如第一种窗口函数的方式直观,所以更推荐第一种方法。
备注:内容来源于stack exchange,提问作者Farzam Pil Aghaee
相关产品推荐
相关产品推荐

