应先桶化再分组:用FLOOR(progress*10)将进度转为0–9整数桶,再CASE映射为可读标签;需WHERE过滤NULL和超限值,并显式ORDER BY保证顺序。

GROUP BY 遇到非标准分组:进度字段不是离散值怎么办
直接用 GROUP BY progress 会把 0.23、0.27、0.71 这类浮点进度值各自分一组,根本达不到“按进度段统计”的目的。必须先做桶化(binning),再聚合。
常见错误是写 GROUP BY ROUND(progress, 1)——看似合理,但 0.25 四舍五入成 0.3,0.24 变成 0.2,边界不一致,统计结果错位。
- 推荐用
FLOOR(progress * 10)转成整数桶:0.0–0.099→0,0.1–0.199→1,…,0.9–0.999→9 - 再用
CASE WHEN映射为可读标签,比如CASE WHEN FLOOR(progress * 10) = 0 THEN '0-10%' … - 别在
SELECT里用别名参与GROUP BY(MySQL 5.7 默认不支持,会报Unknown column)
PostgreSQL / MySQL 8.0+ 的窗口函数替代方案:需要累计分布时别硬 GROUP
如果目标不是“每段多少条”,而是“进度 ≤30% 的任务占总数多少比例”,硬套 GROUP BY + COUNT(*) 再除总行数,既啰嗦又难维护。
更直接的做法是用窗口函数算累计占比:
SELECT FLOOR(progress * 10) AS bucket, COUNT(*) AS cnt, SUM(COUNT(*)) OVER (ORDER BY FLOOR(progress * 10)) * 1.0 / SUM(COUNT(*)) OVER () AS cum_pct FROM tasks GROUP BY bucket;
注意:SUM(COUNT(*)) OVER 是合法的,但 SQLite 不支持,MySQL 5.6 也不支持窗口函数——得降级用自连接或变量模拟。
WHERE 条件漏写导致进度范围失真:特别是 NULL 和 1.0 的处理
任务进度字段常有三种异常值:NULL(未开始)、1.0(已完成)、>1.0(脏数据)。直接 GROUP BY FLOOR(progress * 10) 会让 NULL 分进一组(各数据库行为不一),1.0 进第 10 桶(FLOOR(1.0 * 10) = 10),但你的分段可能只定义到 9(即 90–99%)。
- 务必加
WHERE progress IS NOT NULL AND progress BETWEEN 0 AND 1 - 若想单独统计已完成任务,显式加条件:
OR progress = 1.0,再用CASE单独归类 - 发现
progress > 1.0时,别默默截断——先查出这些记录,修复源头逻辑
聚合后排序混乱:GROUP BY 结果默认无序,别依赖执行顺序
很多人写完 GROUP BY FLOOR(progress * 10) 就以为输出自然按 0、1、2… 排列,实际完全可能乱序(尤其 PostgreSQL 并行查询下)。一旦后续接图表或导出,顺序错乱就难排查。
解决方法只有一个:显式 ORDER BY。
- 按数值桶排:
ORDER BY FLOOR(progress * 10) - 按业务语义排(如 “未开始”、“进行中”、“已完成”):在
CASE中给每个分支加排序权重,或用ORDER BY FIELD()(MySQL) /ORDER BY POSITION()(PostgreSQL) - 别省这行
ORDER BY—— 它不耗性能,但省了它,下游所有环节都得补救
进度分组真正的复杂点不在 SQL 语法,而在于业务定义是否清晰:0.0 算“未开始”还是“初始化中”?0.999 和 1.0 是否应归为同一状态?这些边界没对齐,SQL 写得再漂亮,统计口径也是错的。

















