在数据库报表开发里,经常会碰到这样一种需求:把一笔笔连续数值,比如销售额、耗时、分数,按固定的间距切成一个个区间,再统计每个区间里有多少条记录。这类需求如果写一大串CASE WHEN,不仅啰嗦,而且步长一改就得重写。其实只要用整除相关的运算,就能用很简短的逻辑完成阶梯分组。

为什么用整除可以做阶梯分组
所谓按固定步长分组,本质上是把数轴切成等宽的段。假设步长是10,那么0到9归为一组,10到19归为下一组。如果有一个数值v,我们用整数除法v除以10,得到的商其实就是它落在第几段(从0开始计)。再把商乘回10,就得到了这一段的起始值。这种思路不依赖具体数据库的分段函数,只用基础算术就能表达。
在SQL里,不同数据库提供的整除函数名字不太一样。MySQL里可以用FLOOR(v/step)来模拟向下取整的整除;PostgreSQL有专门的DIV函数;SQL Server里直接写v/step且两端都是整数时就会截断小数。理解了这个原理,我们就能写出与数据库种类弱相关的统计语句。
MySQL中的具体实现
下面以用户年龄按10岁一档统计为例。我们有一张user表,里面有age字段。希望得到每个十岁区间的人数。核心就是先计算组起点,再GROUP BY。
SELECT FLOOR(age / 10) * 10 AS age_group_start, COUNT(*) AS user_count FROM user WHERE age IS NOT NULL GROUP BY FLOOR(age / 10) * 10 ORDER BY age_group_start;
上面的FLOOR(age / 10)先算出年龄属于第几个十岁段,乘10之后变回0、10、20这样的区间起点。GROUP BY后面重复了同样的表达式,数据库会按计算出来的起点聚合。比起写CASE WHEN age >= 0 AND age < 10 THEN 0 ...,这种方式在步长调整为20时,只需要把两个10改成20。
如果要让结果看起来更像“0-9岁”这种标签,可以在SELECT里拼接字符串:
SELECT CONCAT(FLOOR(age / 10) * 10, '-', FLOOR(age / 10) * 10 + 9) AS age_range, COUNT(*) AS user_count FROM user WHERE age IS NOT NULL GROUP BY FLOOR(age / 10) * 10 ORDER BY FLOOR(age / 10) * 10;
PostgreSQL与SQL Server的写法差异
PostgreSQL提供了DIV运算符,语义更清晰。同样的逻辑可以写成下面这样,可读性更好:
SELECT (age DIV 10) * 10 AS age_group_start, COUNT(*) AS user_count FROM user WHERE age IS NOT NULL GROUP BY (age DIV 10) * 10 ORDER BY age_group_start;
在SQL Server中,如果age是INT类型,age / 10本身就会做整数截断,相当于向下取整除法(对正数而言)。所以可以直接写age / 10 * 10。但若age可能为负,SQL Server的整数除法是向零截断,和FLOOR的向下取整不同,这时要用FLOOR(CAST(age AS FLOAT) / 10)来保证和MySQL一致的行为。
负数与边界情况的处理
很多教程只举正例,实际数据可能有负数,比如温度、账户余额。以步长10为例,-5除以10在FLOOR下得到-1,乘10得-10,也就是-5落在-10到-1这段,符合数学上的向下分段。但如果是向零截断的除法,-5/10得0,就会错归到0段。所以在跨数据库或含负数时,统一用FLOOR(数值/步长)最稳妥。
另一个边界是步长不能写0,否则会报除零错误。若步长来自变量,统计前应先在应用层或SQL里用NULLIF过滤。例如FLOOR(age / NULLIF(step, 0)),当step为0时表达式变NULL,该记录可在WHERE里排除。
性能与适用场景分析
从执行计划看,这种表达式分组通常会在扫描时对每行做一次算术计算,开销极小,不会比CASE WHEN更慢。由于使用了确定性函数,部分数据库还能对计算列建索引来加速。它特别适合订单金额分桶(如每100元一档)、响应耗时分区间(每50毫秒一档)等固定间距的统计。
不过当分组边界不规则,比如0到1算一档、1到5算一档,那就不属于固定步长,整除法不再适用,应回到CASE WHEN或区间表关联。明确需求是等宽还是不等宽,才能选对方案。
小结
利用整除运算做SQL阶梯统计,核心就是floor(val/step)*step得到组起点。它写法短、易调整、跨库通用性强。记住正数环境可用数据库原生整除,含负数或要求严格向下时统一用FLOOR。理清这两点,报表里的等宽分桶需求就能轻松搞定。