用SUM()窗口函数计算分组内占比最稳妥,正确写法是分子为当前行值、分母为SUM(SUM(col)) OVER(PARTITION BY group_col),需注意NULL处理、数据类型转换及MySQL版本兼容性。

用 SUM() 窗口函数算分组内占比最稳
直接在 GROUP BY 后用 SUM() 做窗口聚合,是计算分组内占比最可靠的方式。它不依赖外部子查询,也不怕分组字段有 NULL 或重复值干扰。
常见错误是写成 SUM(col) / SUM(SUM(col)) OVER() —— 这会把整个表的总和当分母,不是“分组内”的总和。
- 正确写法:分母必须是同分组内的总和,即
SUM(SUM(col)) OVER(PARTITION BY group_col) - 分子是当前行对应分组的原始值(或聚合后值),比如
sales_amount或SUM(sales_amount) - 注意数据类型:整数除整数可能截断,建议显式转
DECIMAL或乘 1.0
SELECT
region,
product,
sales_amount,
ROUND(
sales_amount * 1.0 / SUM(sales_amount) OVER(PARTITION BY region),
3
) AS ratio_in_region
FROM sales;如果必须用 GROUP BY 聚合后再算比例,记得套两层
当你要先按多列聚合(比如 region + category),再算每个 category 在 region 内的占比,就不能只靠一层 GROUP BY。窗口函数依然可用,但有人习惯用子查询——这时必须套两层,否则分母会错。
- 外层查出各
(region, category)的总销售额 - 内层用
WINDOW或子查询算出每个region的总销售额 - 别在
GROUP BY后直接写SUM(sales)/SUM(SUM(sales))—— 语法不合法,MySQL 8.0+ 也不认
SELECT
region,
category,
total_sales,
ROUND(total_sales * 1.0 / region_total, 3) AS ratio
FROM (
SELECT
region,
category,
SUM(sales_amount) AS total_sales,
SUM(SUM(sales_amount)) OVER(PARTITION BY region) AS region_total
FROM sales
GROUP BY region, category
) t;
COUNT(*) 占比要小心去重和 NULL
算「某类订单数占该用户全部订单数的比例」时,别直接用 COUNT(*) 除,尤其当涉及 LEFT JOIN 或字段可能为 NULL 时。
-
COUNT(col)会忽略NULL,COUNT(*)不会——选哪个取决于你定义的「总体」是否包含空记录 - 如果目标是「有效订单中高优先级订单占比」,分子用
COUNT(CASE WHEN priority='high' THEN 1 END),分母用COUNT(*)或COUNT(order_id),看业务是否排除无效行 - 用窗口函数时,分母应为
COUNT(*) OVER(PARTITION BY user_id),不是COUNT(user_id) OVER(...)—— 后者会漏掉user_id IS NULL的行
MySQL 5.7 没窗口函数?用关联子查询但慎用性能
MySQL 5.7 及更早版本不支持 OVER,只能靠相关子查询模拟,但容易拖慢大表查询。
- 子查询里必须带和外层相同的
WHERE条件来限定分组范围,例如WHERE t2.region = t1.region - 没索引时,每行都触发一次子查询,10 万行就执行 10 万次扫描
- 务必给分组字段(如
region)加索引,否则基本不可用
SELECT
t1.region,
t1.product,
t1.sales_amount,
ROUND(
t1.sales_amount * 1.0 / (
SELECT SUM(t2.sales_amount)
FROM sales t2
WHERE t2.region = t1.region
),
3
) AS ratio_in_region
FROM sales t1;窗口函数不是语法糖,它是把多次扫描变成一次遍历。只要环境允许,优先用 SUM() OVER(PARTITION BY ...),别为了兼容老版本硬扛子查询。分母逻辑一旦写错,比例就全偏了,而且不容易被测试用例发现。

















