如何基于Django查询集中Menu对象的得票数为其分配对应排名
实现方案说明
首先明确:可以通过Django Query Expressions实现该需求,且该方案性能最优,排名逻辑直接在数据库侧执行,无需额外内存计算
方案1:使用Django窗口函数(推荐,Django 2.0+版本支持)
Django内置的Window表达式搭配排名函数,可以直接在查询时生成排名字段,支持两种常用排名规则:
DenseRank:同得分排名相同,排名序号连续不跳号(比如你示例中两个2票的菜单均为第2名,下一个1票的为第3名)Rank:同得分排名相同,排名序号跳号(比如你示例中两个2票的菜单均为第2名,下一个1票的为第4名)
代码实现:
from django.db.models import Q, Window, DenseRank, F # 生成带排名的查询集 qs = Menu.objects.filter( Q(created_at__date__iexact=todays_date) ).annotate( # 按得票数倒序计算密集排名 rank=Window( expression=DenseRank(), order_by=F('votes').desc() ) ).order_by('-votes', 'id')
查询返回的每个Menu对象会自动携带rank属性,序列化时直接把该字段加入序列化器即可直接返回带排名的结果。
方案2:Python内存层处理(兼容低版本Django,适合小数据量场景)
如果你的Django版本低于2.0,或者当日菜单数据量很小,可以直接在Python层遍历查询集分配排名:
qs = Menu.objects.filter( Q(created_at__date__iexact=todays_date) ).order_by('-votes', 'id') current_rank = 1 prev_vote = None same_count = 0 for menu in qs: if menu.votes != prev_vote: current_rank += same_count same_count = 0 # 给对象动态绑定rank属性 menu.rank = current_rank same_count += 1 prev_vote = menu.votes
处理后的查询集对象同样携带rank属性,可直接序列化返回。
示例数据返回效果
按照上述方案处理后,你提供的示例数据会新增rank字段如下:
[ { "id": 10, "file": "https://res.cloudinary.com/dw9bllelz/raw/upload/v1/media/menus/index_ah660c.jpeg", "restaurant": "Burger King", "votes": 10, "created_at": "2021-06-03T09:33:05.505482+03:00", "rank": 1 }, { "id": 9, "file": "https://res.cloudinary.com/dw9bllelz/raw/upload/v1/media/menus/index_ah660c.jpeg", "restaurant": "Texas Plates", "votes": 2, "created_at": "2021-06-03T09:33:05.505482+03:00", "rank": 2 }, { "id": 8, "file": "https://res.cloudinary.com/dw9bllelz/raw/upload/v1/media/menus/index_ah660c.jpeg", "restaurant": "Carlito Dishes", "votes": 2, "created_at": "2021-06-03T09:33:05.505482+03:00", "rank": 2 }, { "id": 7, "file": "https://res.cloudinary.com/dw9bllelz/raw/upload/v1/media/menus/index_ah660c.jpeg", "restaurant": "Kram Dishes", "votes": 1, "created_at": "2021-06-03T09:33:05.505482+03:00", "rank": 3 } ]
内容的提问来源于stack exchange,提问作者Philip Mutua
相关产品推荐
相关产品推荐

