如何用SQL从表中按分组提取累计贡献占比前50%的行
最优SQL实现方案
实现逻辑
完全对齐需求的筛选规则:
- 先按
c1分组算出每组所有c3的总和 - 组内把
c3从大到小排序,从上到下逐行累加c3的值 - 只要某行加入累加队列之前,之前的累加值还没到组总和的50%,这行就必须保留——哪怕加完这行之后累计占比已经超过50%,也要算进去。比如示例里A组单看15的贡献占比才42.8%,没到50%,加上12之后占比到77%,超过了,那这两行都要留。
可直接运行的SQL代码
WITH base_calc AS ( SELECT c1, c2, c3, -- 计算每个c1分组的c3总和 SUM(c3) OVER (PARTITION BY c1) AS group_total, -- 组内按c3降序,逐行累计c3值 SUM(c3) OVER ( PARTITION BY c1 ORDER BY c3 DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS running_total FROM 替换为你的实际源表名 ) -- 筛选条件:累加当前行c3之前的累计值,还没达到组总和的50% SELECT c1, c2, c3 FROM base_calc WHERE running_total - c3 < group_total * 0.5 ORDER BY c1, c3 DESC;
结果校验(对应用户给出的示例数据)
以A分组为例验证:
- 组内c3总和 = 15+12+8 = 35,50%阈值为17.5
- c3=15的行:累计值为15,减去当前行c3后为0,0<17.5,保留
- c3=12的行:累计值为15+12=27,减去当前行c3后为15,15<17.5,保留
- c3=8的行:累计值为27+8=35,减去当前行c3后为27,27>17.5,过滤
最终筛出来的结果和示例期望输出完全一致。
方案特点
- 性能最优:全流程仅对源表做一次扫描,所有分组统计、累计计算都靠窗口函数在单次扫描中完成,没有自连接、多层嵌套子查询导致的重复扫表开销,千万级以上数据量下的运行效率远高于传统写法
- 无边界偏差:不管是累计值刚好等于50%、还是组内存在多个相同c3值的场景,筛选结果都不会出错
- 适配性强:所有支持标准窗口函数的SQL引擎(MySQL 8.0+、PostgreSQL、SQL Server、Hive、Spark SQL、ClickHouse等)都能直接运行,无需额外语法适配
如果碰到不支持窗口函数的老旧环境(比如MySQL 5.x),需要通过同组自连接的方式计算累计值,但这种写法性能较差,仅适合小数据量场景临时使用
内容的提问来源于stack exchange,提问作者Kallol
相关产品推荐
相关产品推荐

