数值处理是SQL开发里出现频率极高的一类需求,Oracle提供的round、trunc、mod、ceil、floor这五个函数几乎覆盖了日常取整、舍入和取余的全部场景。它们语法都不复杂,但容易踩坑的地方藏在细节里:round对负数是往哪边舍入的?trunc截断负数时方向如何?mod在Oracle里对负数取余,结果符号跟谁走?这些问题不弄清楚,报表里差一分钱、分页时漏一页的情况就会反复出现。下面结合可以直接执行的示例,把这几个函数的行为规则和典型用法完整梳理一遍。

一、round函数:四舍五入的规则与负数位数
round的完整语法是ROUND(n, integer),n是待处理的数值,integer指定舍入到的小数位数,不写时默认为0,也就是舍入到整数位。Oracle的round采用四舍五入、逢五远离零的规则,这一点和部分编程语言里的银行家舍入不同,做金额计算时尤其要注意。比如ROUND(2.5)的结果是3,ROUND(-2.5)的结果是-3而不是-2。
integer参数还可以传负数,表示舍入到十位、百位、千位,这是不少人没用过的特性,在数据汇总展示时非常实用,比如把销售额统一舍入到万元再出报表。看下面这组示例:
SELECT ROUND(3.14159, 2) AS r1, -- 结果 3.14
ROUND(3.145, 2) AS r2, -- 结果 3.15
ROUND(123.456, 0) AS r3, -- 结果 123
ROUND(125.456, -1) AS r4, -- 结果 130
ROUND(123.456, -2) AS r5, -- 结果 100
ROUND(-2.5) AS r6 -- 结果 -3
FROM dual;
从结果可以看出,保留两位小数时看第三位是否达到5;位数参数为-1时看个位,为-2时看十位,规则一致。还有一个容易被忽略的点:Oracle的number类型按十进制存储,ROUND(3.145, 2)能稳定得到3.15,不会像二进制浮点数那样在中间环节出现3.1449999之类的误差,这也是金额字段建议用number而不是binary_double的原因之一。另外,如果业务要求银行家舍入也就是逢五取偶,round满足不了,需要自己写case表达式处理,直接套round会造成系统性的单向偏差。
二、trunc函数:只砍不进的截断处理
trunc的语法和round一样是TRUNC(n, integer),但行为完全不同:它不做任何舍入,只是把指定位数之后的数字直接砍掉,方向永远朝零。对正数来说效果很直观,TRUNC(3.9999, 2)得到3.99;对负数则表现为朝零靠拢,TRUNC(-8.75)得到-8而不是-9,这一点必须和floor严格区分开。
SELECT TRUNC(3.9999, 2) AS t1, -- 结果 3.99
TRUNC(-3.999, 2) AS t2, -- 结果 -3.99
TRUNC(123.456, -1) AS t3, -- 结果 120
TRUNC(199.99, -2) AS t4, -- 结果 100
TRUNC(-8.75) AS t5 -- 结果 -8
FROM dual;
和round对比着记更清楚:同样处理3.456保留一位小数,round看第二位是5就进位得3.5,trunc不管后面是什么直接砍成3.4。两者相差的这0.1在大量数据累加时会被放大,所以统计口径必须提前统一,是舍入还是截断要在需求阶段定死。trunc在业务里最常见的用途是金额抹零,比如财务要求金额只保留两位小数且不做四舍五入,用TRUNC(amount, 2)就比round更符合口径。
此外,trunc还是少数能同时处理日期的函数,TRUNC(SYSDATE)会把时间部分清零只保留日期,TRUNC(SYSDATE, 'mm')返回当月第一天,按天、按月做分组统计时经常和它打交道,很多按日汇总的报表底层都靠它来归一化时间。
三、ceil与floor:方向相反的一对取整函数
ceil和floor都只接收一个参数,语义很纯粹:ceil返回大于等于n的最小整数,也就是向上取整;floor返回小于等于n的最大整数,向下取整。正数场景没什么悬念,CEIL(3.01)是4,FLOOR(3.99)是3。真正的分水岭在负数:CEIL(-3.2)返回-3,FLOOR(-3.0001)返回-4,因为-3是大于等于-3.2的最小整数,-4是小于等于-3.0001的最大整数。记不清的时候在脑中画一条数轴,取整方向立刻就清楚了。
SELECT CEIL(3.0001) AS c1, -- 结果 4
CEIL(3) AS c2, -- 结果 3
CEIL(-3.2) AS c3, -- 结果 -3
FLOOR(3.9999) AS f1, -- 结果 3
FLOOR(-3.0001) AS f2, -- 结果 -4
FLOOR(-3) AS f3 -- 结果 -3
FROM dual;
这两个函数最典型的应用是分页计算。假设总记录数为total,每页显示pageSize条,总页数应该写成CEIL(total / pageSize),这样99条数据按每页10条会正确算出10页;如果误用floor就会漏掉最后一页不满的数据。反过来,在计算满页数量、整块分配资源时用floor更合适,方向选错结果就差一整页或一整块。
ceil还经常和除法配合实现向上取整到某个倍数的需求。比如内存分配要凑整到100的倍数、物流计费按50公斤向上凑整,写法是CEIL(n / 100) * 100,对123来说结果是200。这种组合在容量规划、阶梯计费类需求里出现频率很高,比单纯记住ceil的取整规则更有实用价值。
四、mod函数:取余结果的符号规则
mod的语法是MOD(n2, n1),返回n2除以n1的余数。正数场景没有争议,MOD(10, 3)等于1。但Oracle的mod在负数上的行为和Java、C这类语言的百分号运算不一样:它基于floor语义计算,结果的正负号跟随除数n1。也就是说MOD(-10, 3)的结果是2而不是-1,因为-10除以3在floor语义下商取-4,余数算出来就是2。
SELECT MOD(10, 3) AS m1, -- 结果 1
MOD(-10, 3) AS m2, -- 结果 2
MOD(10, -3) AS m3, -- 结果 -2
MOD(-10, -3) AS m4, -- 结果 -1
MOD(10, 0) AS m5 -- 结果 10,除数为0时返回被除数
FROM dual;
还有一个细节:当除数为0时,mod不报错,而是直接返回被除数本身。这个设计在写通用逻辑时可以省去判空,但也可能掩盖问题,如果业务上除数本来就不应该为0,最好还是显式校验。mod的常见用法包括用MOD(id, 2)判断奇偶做隔行处理或分组,用MOD(rownum, 500)控制批量提交的节奏,以及按周期轮询拆分任务等。
五、五个函数的行为对比与组合用法
把五个函数放在一起对比,核心差异一目了然:
| 函数 | 作用 | 示例 | 结果 |
| round | 四舍五入,逢五远离零 | round(3.456, 1) | 3.5 |
| trunc | 按位截断,方向朝零 | trunc(3.456, 1) | 3.4 |
| ceil | 向上取整 | ceil(3.01) | 4 |
| floor | 向下取整 | floor(3.99) | 3 |
| mod | 取余,符号跟随除数 | mod(-10, 3) | 2 |
实际业务里它们经常组合出现。比如一张订单表要按客户统计金额,要求金额保留两位小数且抹零、平均值四舍五入到整数、按每页20条算总页数、订单号尾号奇偶分流,一条SQL就能完成:
SELECT customer_id,
TRUNC(SUM(amount), 2) AS total_amount, -- 金额抹零,不做四舍五入
ROUND(AVG(amount), 0) AS avg_amount, -- 平均值四舍五入到整数
CEIL(COUNT(*) / 20.0) AS total_pages, -- 每页20条的总页数
CASE WHEN MOD(order_id, 2) = 0
THEN 'A通道' ELSE 'B通道' END AS channel -- 尾号奇偶分流
FROM t_order
GROUP BY customer_id;
这里有个小细节值得说明:Oracle的number除法默认保留小数,99除以20会得到4.95,所以CEIL(COUNT(*) / 20.0)能正确算出5页,把20写成20.0只是让意图更明显。金额口径统一用trunc还是round,取决于业务对误差的容忍方向,宁可少算一分还是多算一分,要在需求评审时就定死,不然月底对账时两边数字对不上,排查起来非常费劲。
总的来说,round管舍入、trunc管截断、ceil和floor管方向、mod管余数,五个函数各司其职。写SQL之前把负数行为和位数参数这些边界规则确认一遍,比出了问题再回头翻文档要省事得多。
Oracle数值函数round函数取整函数修改时间:2026-09-30 19:02:54