正确做法是用SUM() OVER()将全表总和“广播”到每行再除法,避免GROUP BY后分母丢失;需同步WHERE条件、乘100.0防整数除法、用ROUND()四舍五入。

用 SUM() 和窗口函数算组内占比
直接在 GROUP BY 后除以总数会出错——因为聚合后无法直接访问全表总和。正确做法是用窗口函数 SUM() OVER() 把总数“广播”到每行,再做除法。
常见错误是写成 COUNT(*) / (SELECT COUNT(*) FROM table),这在复杂查询里容易漏掉 WHERE 条件,导致分母不准。
示例:统计各城市订单数占总订单数的比例
SELECT city, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) AS pct FROM orders GROUP BY city;
注意三点:
• SUM(COUNT(*)) OVER() 是合法的嵌套聚合,窗口函数作用于分组结果
• 必须乘 100.0(带小数点),否则整数除法结果为 0
• ROUND(..., 2) 是四舍五入,不是截断
ROUND() 和 CAST() 在小数位处理上的区别
ROUND() 是最常用也最安全的选择;CAST(x AS DECIMAL(5,2)) 虽然也能保留两位,但遇到超限值(如 99.995)会报错或截断,且不同数据库对精度定义不一致。
容易踩的坑:
• PostgreSQL 中 ROUND(12.345, 2) → 12.35,但 ROUND(12.345::numeric, 2) 才稳定,隐式类型转换可能出意外
• MySQL 8.0+ 支持 ROUND() 直接用于聚合结果,老版本需嵌套子查询
• SQLite 的 ROUND() 默认返回 REAL 类型,显示可能多出尾随零(如 25.000000),建议外层再转字符串格式化
WHERE 条件影响分母时必须同步过滤
如果查询加了 WHERE status = 'completed',那么分母也必须只算已完成订单,否则占比失真。窗口函数天然继承外层 WHERE,但子查询方式容易漏掉这点。
正确写法:
SELECT category, COUNT(*) AS cat_cnt, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) AS pct FROM products WHERE is_active = 1 -- 这个条件同时作用于分子和分母 GROUP BY category;
错误写法(分母未过滤):
-- ❌ 危险:分母是全表总数,分子只是 active 的,结果总和远超 100% COUNT(*) * 100.0 / (SELECT COUNT(*) FROM products)
MySQL 中避免 DIV 导致整数除法归零
MySQL 默认开启 sql_mode=STRICT_TRANS_TABLES 时,5 / 10 结果是 0,不是 0.5。这是新手最常卡住的地方。
解决方案只有两个:
• 强制转浮点:COUNT(*) * 100.0 / SUM(COUNT(*)) OVER()
• 显式类型转换:CONVERT(COUNT(*), DECIMAL) * 100 / SUM(COUNT(*)) OVER()
• 绝对不要用 DIV(它是整数除法专用运算符)
另外,MySQL 5.7 不支持在窗口函数中直接嵌套聚合,必须升级到 8.0+ 或改用变量模拟,这点容易被忽略。

















