You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django ORM annotate中如何对两个Rank窗口函数值求和解决SQLite报错

问题原因

SQLite 对窗口函数的使用有严格限制:不允许在同一个SELECT子句中引用另一个窗口函数的计算结果,也不允许将窗口函数的输出作为参数,用于同一层级的其他计算或是另一个窗口函数的排序逻辑。
你之前的写法将所有字段都放在同一个annotate中,Django 会将它们生成到同一个SELECT子句里,因此触发了SQLite的限制抛出错误。

解决方案

将查询拆分为两个层级:第一层先计算基础聚合字段和两个单项排名,第二层基于第一层的结果计算总排名分和总排名。可以通过子查询的方式实现,无需引入第三方依赖:

from django.db.models import Subquery, OuterRef, F, Count, Avg, Window, Rank, Cast

class ChampionKillManager(BaseManager):
    def get_queryset(self):
        # 第一层查询:计算基础聚合和两个单项排名
        base_queryset = super().get_queryset().annotate(
            length=Count('sequence'),
            avg_damage_contribution=Avg('sequence__damage_contribution'),
            avg_interval=Cast(Max('sequence__time') - Min('sequence__time'), models.FloatField()) / F('length'),
            rank_damage_contribution=Window(Rank(), order_by=F('avg_damage_contribution').desc()),
            rank_interval=Window(Rank(), order_by=F('avg_interval').asc()),
        )
        
        # 第二层查询:基于第一层的结果计算总得分和总排名
        return super().get_queryset().annotate(
            # 从子查询关联获取第一层计算的字段
            length=Subquery(base_queryset.filter(pk=OuterRef('pk')).values('length')[:1]),
            avg_damage_contribution=Subquery(base_queryset.filter(pk=OuterRef('pk')).values('avg_damage_contribution')[:1]),
            avg_interval=Subquery(base_queryset.filter(pk=OuterRef('pk')).values('avg_interval')[:1]),
            rank_damage_contribution=Subquery(base_queryset.filter(pk=OuterRef('pk')).values('rank_damage_contribution')[:1]),
            rank_interval=Subquery(base_queryset.filter(pk=OuterRef('pk')).values('rank_interval')[:1]),
            # 计算总排名分
            total_rank_score=F('rank_damage_contribution') + F('rank_interval'),
            # 生成总排名
            rank_total=Window(Rank(), order_by=F('total_rank_score').asc()),
        )

如果你使用的Django版本 >= 3.2,也可以使用CTE(公共表表达式)实现,性能比子查询更优:

from django.db.models import Count, Avg, Window, Rank, Cast, F
from django.db.models.expressions import CTE

class ChampionKillManager(BaseManager):
    def get_queryset(self):
        # 定义CTE存储基础聚合和两个单项排名
        base_cte = CTE(
            super().get_queryset().annotate(
                length=Count('sequence'),
                avg_damage_contribution=Avg('sequence__damage_contribution'),
                avg_interval=Cast(Max('sequence__time') - Min('sequence__time'), models.FloatField()) / F('length'),
                rank_damage_contribution=Window(Rank(), order_by=F('avg_damage_contribution').desc()),
                rank_interval=Window(Rank(), order_by=F('avg_interval').asc()),
            )
        )
        
        # 关联CTE计算总得分和总排名
        return super().get_queryset().join(
            base_cte, on=base_cte.col.pk == OuterRef('pk')
        ).annotate(
            length=base_cte.col.length,
            avg_damage_contribution=base_cte.col.avg_damage_contribution,
            avg_interval=base_cte.col.avg_interval,
            rank_damage_contribution=base_cte.col.rank_damage_contribution,
            rank_interval=base_cte.col.rank_interval,
            total_rank_score=F('rank_damage_contribution') + F('rank_interval'),
            rank_total=Window(Rank(), order_by=F('total_rank_score').asc()),
        ).with_cte(base_cte)

内容的提问来源于stack exchange,提问作者gypark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 13:06:04