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

如何用Django ORM实现MariaDB按星期分组的营收占比查询

我来帮你解决这个Django ORM实现营收占比的问题!先分析下你遇到的核心问题,再给出几种可行的解决方案:

问题根源

你原始的MariaDB SQL逻辑是先按周几分组计算每组营收,再用每组营收除以整张表的总营收得到占比。之前的尝试失败原因:

  • RawSQL写法:你直接在values()里用ExtractIsoWeekDay,导致Django没有正确触发GROUP BY逻辑,所以只返回了单个值;
  • Window函数写法:直接用Sum('revenue')/Window(Sum('revenue'))时,窗口函数是在分组前的全量数据上计算,而非分组后的总和,所以结果错误。

解决方案

解法1:用子查询获取总营收(兼容性最好)

这种方法先通过子查询一次性获取整张表的总营收,再分组计算每组占比,逻辑清晰且兼容大多数Django版本:

from django.db.models import Sum, F, ExtractIsoWeekDay, Subquery

# 用Subquery获取总营收(避免两次数据库查询)
total_revenue_subq = Subquery(
    Orders.objects.values('id')  # 任意字段,仅用于触发聚合
    .annotate(total=Sum('revenue'))
    .values('total')[:1]
)

# 分组计算营收占比
share = (
    Orders.objects
    .annotate(week_day=ExtractIsoWeekDay('date'))  # 提取周几(1=周日,7=周六,和DAYOFWEEK一致)
    .values('week_day')  # 按周几分组
    .annotate(
        group_revenue=Sum('revenue'),
        total_revenue=total_revenue_subq
    )
    .annotate(revenue_share=F('group_revenue') / F('total_revenue'))  # 计算占比
    .values('week_day', 'revenue_share')  # 保留需要的字段
    .order_by('week_day')
)

解法2:正确使用Window函数(贴近原始SQL)

如果你用的是Django 2.0+且MariaDB版本≥10.2(支持窗口函数),可以用这种更贴近原始SQL的写法:

from django.db.models import Window, Sum, F, ExtractIsoWeekDay

share = (
    Orders.objects
    .annotate(week_day=ExtractIsoWeekDay('date'))
    .values('week_day')
    .annotate(group_revenue=Sum('revenue'))  # 先分组计算每组营收
    .annotate(
        # 窗口函数计算所有分组的营收总和(即总营收),partition_by为空表示整个结果集
        total_revenue=Window(Sum('group_revenue'), partition_by=[])
    )
    .annotate(revenue_share=F('group_revenue') / F('total_revenue'))
    .values('week_day', 'revenue_share')
    .order_by('week_day')
)

解法3:修正RawSQL写法(严格匹配原始SQL)

如果你需要完全复用原始SQL的逻辑,可以修正RawSQL的写法,确保触发GROUP BY:

from django.db.models import RawSQL, ExtractIsoWeekDay

share = (
    Orders.objects
    .annotate(week_day=ExtractIsoWeekDay('date'))
    .values('week_day')  # 触发GROUP BY
    .annotate(
        revenue_share=RawSQL(
            'SUM(revenue) / SUM(SUM(revenue)) OVER ()',
            []  # 无占位符,传空列表
        )
    )
    .order_by('week_day')
)

注意事项

  • ExtractIsoWeekDay返回的是1(周日)到7(周六),和MariaDB的DAYOFWEEK函数行为一致,无需额外调整;
  • 如果总营收为0,需要处理除以0的异常,可以加个条件判断或者用NullIf函数;
  • 优先推荐解法1或2,RawSQL可读性差且不利于跨数据库迁移。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 14:53:10