用窗口函数SUM() OVER()计算占比最稳妥,需搭配GROUP BY、处理NULL和除零、显式转浮点并ROUND控制精度,避免WHERE条件不一致或在窗口中误用LIMIT。

用窗口函数 SUM() OVER() 算总销售额再除,最稳
直接在 GROUP BY 后用子查询或 CTE 算总和容易出错,尤其当分类字段有 NULL 或过滤条件不一致时。窗口函数能确保分母是当前查询上下文下的真实总和,不会受 WHERE 或分组影响。
常见错误是写成 SUM(sales) / SUM(sales) OVER() 却忘了加 GROUP BY,结果每行都返回 1;或者漏了 ROUND() 导致小数位太多,报表难读。
- 必须搭配
GROUP BY category(或其他分类字段) - 分子用
SUM(sales),分母用SUM(SUM(sales)) OVER()—— 注意是两层SUM - 建议用
ROUND(..., 4)控制精度,避免浮点误差累积
SELECT category, SUM(sales) AS total_sales, ROUND(SUM(sales) * 1.0 / SUM(SUM(sales)) OVER(), 4) AS ratio FROM orders GROUP BY category;
NULL 分类要单独处理,否则占比会失真
如果 category 字段允许 NULL,默认会被归为一类参与计算,但业务上常把它视为“未分类”或“无效数据”,不该计入分母。不处理会导致其他分类占比被系统性压低。
- 先用
WHERE category IS NOT NULL过滤掉无效值,再算占比 - 若需保留
NULL行,得用CASE WHEN单独算它的占比,并确保分母不含它 - 注意:MySQL 8.0+ 和 PostgreSQL 支持
FILTER子句,但多数场景还是显式WHERE更可控
不同数据库对除零和小数精度的处理差异明显
PostgreSQL 默认整数除法截断,5 / 10 得 0;SQL Server 会自动转为浮点;SQLite 不支持窗口函数(得用子查询)。这些细节不注意,同一段 SQL 在不同环境跑出不同结果。
- 强制转浮点:写成
SUM(sales) * 1.0或CAST(SUM(sales) AS DECIMAL(10,4)) - 防除零:加
CASE WHEN SUM(SUM(sales)) OVER() = 0 THEN 0 ELSE ... END - MySQL 用户注意:
SUM() OVER()从 8.0 开始支持,5.7 及更早版本必须用变量或自连接模拟
想加排序或限制 Top N?别在窗口里套 LIMIT
有人想“只看占比前 3 的分类”,就直接在窗口查询后加 LIMIT 3,结果发现占比加起来远小于 1 —— 因为 LIMIT 是最后执行,分母仍是全部分类总和,分子却是筛选后的几个。
- 正确做法:先算出所有分类占比,再用外层查询
ORDER BY ratio DESC LIMIT 3 - 如果真要动态 Top N 占比(比如“累计占比超 80% 的前 N 类”),就得用
SUM(ratio) OVER (ORDER BY ratio DESC)做累计,再过滤 - 别试图在
OVER()里加WHERE或LIMIT,语法不合法
实际跑的时候,先确认你的数据库版本是否支持窗口函数,再检查分类字段有没有隐式 NULL 或空字符串混入,这两处最容易让结果看起来“差不多但就是不对”。

















