Django查询近3个月每月发帖Top5用户时QuerySet报错如何解决?
问题描述
需求为获取近3个月每个月发帖量排名前5的用户,总计返回15条用户数据。
原有查询语句如下:
Post.objects.filter(issued_at__isnull=False, issued_at__gte=three_months_ago).annotate( month=TruncMonth('issued_at'))[:5].values('month').annotate(num_of_posts=Sum('number_of_posts')).values( 'month', 'user__name', 'num_of_posts').order_by('-num_of_posts')[:15]
运行时触发AssertionError: Cannot reorder a query once a slice has been taken.报错,即使去掉切片直接取15条,返回结果也不符合每月前5的规则。
错误原因
- 查询语句在
annotate(month=TruncMonth('issued_at'))后直接加了[:5]切片,会提前截断查询集,Django不允许对已经切片的查询集再执行排序、分组这类修改操作,因此直接触发断言报错。 - 如果直接删除切片后全局排序取前15,是把3个月所有用户的发帖量混合排序,无法保证每个月都有5个名额,不符合需求规则。
正确实现方案
方案1:窗口函数单查询实现(推荐,Django 2.0+ 支持)
利用窗口函数的分区排名能力,一次查询直接得到每个月的前5名,性能更高:
首先导入依赖:
from django.db.models import Sum, Rank, F, Window from django.db.models.functions import TruncMonth
查询语句:
result = Post.objects.filter( issued_at__isnull=False, issued_at__gte=three_months_ago ).annotate( # 按发布时间截断到月份 month=TruncMonth('issued_at') ).values( # 按月份、用户分组 'month', 'user' ).annotate( # 统计每个用户当月发帖总量 num_of_posts=Sum('number_of_posts') ).annotate( # 按月份分区,每个分区内按发帖量倒序排名 rank=Window( expression=Rank(), partition_by=F('month'), order_by=F('num_of_posts').desc() ) ).filter( # 只取每个月排名前5的用户 rank__lte=5 ).values( # 返回需要的字段 'month', 'user__name', 'num_of_posts' ).order_by('month', 'rank')
方案2:分月查询拼接(易理解,兼容性好)
如果对窗口函数不熟悉,可以单独查询每个月的前5名再拼接结果,逻辑清晰不容易出错:
from datetime import datetime from dateutil.relativedelta import relativedelta result = [] # 遍历近3个月 for offset in range(3): # 计算当月的起始和结束时间 month_start = (datetime.today() - relativedelta(months=offset)).replace(day=1, hour=0, minute=0, second=0, microsecond=0) next_month_start = month_start + relativedelta(months=1) # 查询当月发帖量前5的用户 month_top5 = Post.objects.filter( issued_at__isnull=False, issued_at__gte=month_start, issued_at__lt=next_month_start ).values( 'user__name' ).annotate( num_of_posts=Sum('number_of_posts') ).order_by('-num_of_posts')[:5] # 补全月份字段加入结果集 for item in month_top5: item['month'] = month_start result.append(item)
内容的提问来源于stack exchange,提问作者Ahmad Hussian
相关产品推荐
相关产品推荐

