ABC分类是按销售额累计占比划分客户/商品等级:A类占60%、B类占30%、C类占10%;不能用GROUP BY因其打散行序,破坏窗口函数SUM() OVER (ORDER BY ...)所需的全局有序累积。

什么是ABC分类,为什么不能用GROUP BY直接做
ABC分类本质是按销售额(或其他指标)累计占比划分客户/商品等级:A类占累计60%、B类占接下来30%、C类占剩余10%。很多人第一反应是先GROUP BY聚合再排序,但问题在于——累计占比必须基于全局有序的行级累积,而GROUP BY会打散原始行序,无法支撑SUM() OVER (ORDER BY ...)这类窗口计算。
关键点在于:ABC分类不是分组统计,而是对明细行按指标排序后,逐行累加并打标。
核心SQL结构:ORDER BY + SUM() OVER + CASE WHEN
实际执行分三步走,缺一不可:
- 先按销售额降序排列所有行(
ORDER BY sales DESC) - 用
SUM(sales) OVER (ORDER BY sales DESC)算出每行的累计销售额 - 再用
SUM(sales) OVER ()拿到总销售额,二者相除得累计占比 - 最后用
CASE WHEN按阈值打标(如累计占比≤0.6 → 'A')
SELECT
item_id,
sales,
SUM(sales) OVER (ORDER BY sales DESC) AS cum_sales,
SUM(sales) OVER () AS total_sales,
ROUND(SUM(sales) OVER (ORDER BY sales DESC) * 1.0 / SUM(sales) OVER (), 4) AS cum_ratio,
CASE
WHEN SUM(sales) OVER (ORDER BY sales DESC) * 1.0 / SUM(sales) OVER () <= 0.6 THEN 'A'
WHEN SUM(sales) OVER (ORDER BY sales DESC) * 1.0 / SUM(sales) OVER () <= 0.9 THEN 'B'
ELSE 'C'
END AS abc_class
FROM sales_detail;
注意:SUM(sales) OVER ()必须写两次(一次算总数,一次参与除法),不能用别名替代——多数数据库不支持在SELECT子句中引用同级别名。
容易踩的坑:NULL、重复值、排序稳定性
- 如果
sales字段含NULL,ORDER BY sales DESC会把它们排在最末(或最前,取决于数据库),导致累计值跳变。务必加WHERE sales IS NOT NULL过滤
- 多个商品销售额相同时,
ORDER BY sales DESC不保证稳定排序,可能让同分项在不同执行中被分到不同ABC类。建议追加唯一键排序,如ORDER BY sales DESC, item_id
- 累计占比计算时,整数除法在某些数据库(如MySQL旧版本)会截断小数。显式乘
1.0或用CAST(sales AS DECIMAL)避免
-
SUM() OVER (ORDER BY ...)默认等价于SUM() OVER (ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),无需显式写,但别误写成RANGE——它会对相同值“合并累加”,破坏行级语义
如果数据量大,怎么避免性能崩掉
sales字段含NULL,ORDER BY sales DESC会把它们排在最末(或最前,取决于数据库),导致累计值跳变。务必加WHERE sales IS NOT NULL过滤ORDER BY sales DESC不保证稳定排序,可能让同分项在不同执行中被分到不同ABC类。建议追加唯一键排序,如ORDER BY sales DESC, item_id
1.0或用CAST(sales AS DECIMAL)避免SUM() OVER (ORDER BY ...)默认等价于SUM() OVER (ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),无需显式写,但别误写成RANGE——它会对相同值“合并累加”,破坏行级语义窗口函数本身不索引友好,ORDER BY字段没索引时,全表排序开销巨大:
- 确保
sales字段有索引(单列或作为联合索引前导列) - 若只关心Top N(比如只标前1000个A类),可在子查询里先
LIMIT再算累计——但注意:这会让累计占比失真,仅适用于近似场景 - 更稳妥的做法是把累计逻辑下推到应用层:用游标遍历排序结果,边读边累加。适合千万级以上明细且对实时性要求不高的批处理
ABC分类真正的复杂点不在SQL语法,而在业务定义是否闭环:累计占比阈值是固定值还是动态调整?是否要排除退货/测试订单?这些逻辑一旦掺进SQL,就很难维护。不如把基础累计交给数据库,分级规则交给配置表或代码判断。

















