必须用 ROUND(numerator * 100.0 / denominator, 2) 配合窗口函数算占比,分母用 SUM() OVER(PARTITION BY ...) 获取,乘数 100.0 确保浮点运算,再处理 NULL 和除零。

怎么用 ROUND() 配合窗口函数算占比,避免小数精度翻车
直接除法算占比常出现 0.3333333333333333、结果加起来不等于 100% 的问题,本质是浮点精度和显示截断没分离。必须先用窗口函数算出分母(如 SUM() OVER()),再用 ROUND(numerator * 100.0 / denominator, 2) —— 注意 100.0 而不是 100,否则整数除法在 PostgreSQL/MySQL 8.0+ 之前会截断小数。
常见错误现象:ROUND(col / SUM(col) OVER() * 100, 2) 在某些数据库里因运算顺序或隐式类型转换导致结果为 0;或者用 CAST(... AS DECIMAL(5,2)) 却忘了先乘 100.0,小数位被吃掉。
- 始终把乘数
100.0放在除法前面或中间,确保参与运算的是浮点数 - 如果要保证合计 = 100%,得额外处理四舍五入后的尾差(比如把最大一项的占比补上差额)
- SQLite 不支持窗口函数,别在这儿试
SUM() OVER(),会报near "OVER": syntax error
按多个维度分组再算占比:嵌套 PARTITION BY 怎么写才不漏数据
比如“每个城市每种商品类别的销量占该城市总销量的百分比”,不能只写 PARTITION BY city 就完事——那只是城市级占比;也不能写成 PARTITION BY city, category,那分母变成单个品类销量,毫无意义。
正确做法是两层:外层按 city 分组聚合,内层用窗口函数对同一 city 再算各 category 占比。关键在 PARTITION BY 只写控制分母的维度,和 GROUP BY 的字段保持逻辑一致。
SELECT city, category, SUM(sales) AS cat_sales, ROUND(SUM(sales) * 100.0 / SUM(SUM(sales)) OVER (PARTITION BY city), 2) AS pct- 注意这里用了
SUM(SUM(sales)) OVER(...):内层SUM(sales)是分组聚合,外层SUM()是窗口函数,语法合法且必要 - 如果漏掉
PARTITION BY city,分母变成全表总销量,就不是“城市内占比”了
NULL 值和零值会让占比计算崩在哪几个地方
当分子或分母含 NULL,整个占比列会变 NULL;当某组总销量为 0(比如促销期某城市没卖出去任何东西),division by zero 错误直接中断查询(PostgreSQL 报错,MySQL 可能返回 NULL 或警告)。
这不是“数据脏”的问题,而是 SQL 标准行为。必须显式处理,不能依赖前端补零。
- 用
COALESCE(sales, 0)替换分子中的NULL,但注意:这会把真实缺失和业务零值混同 - 分母用
NULLIF(SUM(...) OVER(...), 0),让除零时返回NULL而非报错;再套一层COALESCE(..., 0)统一置 0 - 更稳妥的做法是在
WHERE阶段提前过滤掉无效分组(如HAVING SUM(sales) > 0),减少窗口函数无效计算
性能敏感场景下,COUNT(*) OVER() 和 SUM(1) OVER() 有区别吗
没有本质区别,两者都走相同执行计划,现代数据库(PostgreSQL 12+、MySQL 8.0+、SQL Server 2017+)都会优化成轻量计数。但写 COUNT(*) OVER() 更符合语义直觉,也更易读。
真正影响性能的是窗口函数的 PARTITION BY 字段基数。比如按用户 ID 分组算每个用户的订单占比,而用户量是千万级,这时窗口排序开销会明显上升。
- 避免在高基数字段(如
user_id、order_no)上做PARTITION BY算占比,优先降维到业务有意义的层级(如region、product_category) - 如果必须按高基字段算,确认数据库已建对应索引,且统计信息准确(
ANALYZE过) - 别为了“看起来快”把窗口函数拆成子查询 + JOIN,多数情况下执行计划更差,还容易引入笛卡尔积
实际跑的时候,最常卡住的不是语法,而是没意识到 PARTITION BY 的粒度和业务口径是否真的一致——比如“华东区销售额占比”写成 PARTITION BY province,结果算出来是每个省占全国的比,而不是华东四省内部比。这种错,查数据时根本看不出来。

















