前言
遇到一个比较奇葩的需求,数据表有两个字段类型都是number(18,6),A字段存储的是未经处理的数字,比如0.233350这样的;B字段存储的是由程序经过四舍五入后的数字,比如0.233000。实际上用户输入的数据得出的结果确实是0.233350。
因为业务的需要,当比对两个字段的值有差异时,系统会认为有异常。
程序是第三方公司写的,改不了代码,那就只能从数据库开始着手了。
根据小数的特点,有以下几个思路
思路一、判断小数部分有效长度
比如0.233000的小数部分有效长度是3;0.233300的小数部分有效长度是4。
特殊情况,当数字是一个整数时,比如 100.000000的小数部分有效长度是0。
那么我们来构造一下SQL语句
分别以数字999.233000、999.233300以及999.000000来举例
select 999.233000 fNumber,
case when instr(to_char(999.233000),'.')=0 then 0
else
length(substr(to_char(999.233000),instr(to_char(999.233000),'.')+1,length(to_char(999.233000))-instr(to_char(999.233000),'.')))
end fLength
from dual
999.233000得到的结果是
999.233300得到的结果是
999.000000得到的结果是
这个结果是符合预期的。
这种思路的判断条件是数位是否超过3,超过3的表示有3位以上的数位。
思路二、不判断有效数位,利用字符串的特性判断最后三位是否等于000
注意:这个思路是有前提的,只适用于特定精度情况下。
如果直接把小数用to_char函数转成字符串,会自动把后面的0去掉;
那么如何判断呢?首先根据字段类型可以知道,精度是6位小数,那么只需要把小数转成整数即可使用to_char函数转字符串,而不会丢失精度。
同样以数字999.233000、999.233300以及999.000000来举例
select 999.233000 fNumber,to_char(999.233000*1000000) fmultiNumber,substr(to_char(999.233000*1000000),-3,3) fLast3Char from dual
999.233000得到的结果是
999.233300得到的结果是
999.000000得到的结果是
同样地,结果也是符合预期的。
这种思路的判断条件是FLAST3CHAR是否等于000。
需要注意的是这种思路中,相乘的那个基数是受字段的精度影响的,比如如果字段是number(18,3)的话,那么就要乘以1000。
思路三、将小数转换成整数,再除以1000,判断结果是否为小数
这是在思路二的基础上发展而来,思路二中字段乘以1000000后会变成一个整数,如果进一步再除以1000的话,判断结果是否为小数就能解决问题。
同样以数字999.233000、999.233300以及999.000000来举例
select 999.233000 fNumber,to_char(999.233000*1000000/1000) fmultiNumber,
instr(to_char(999.233000*1000000/1000),'.') fPointIndex
from dual
999.233000得到的结果是
999.233300得到的结果是
999.000000得到的结果是
同样结果也是符合预期的。
这种思路只需要判断FPOINTINDEX是否大于0就可以。
与思路二一样,这种思路中,相乘的那个基数也是受字段的精度影响的,比如如果字段是number(18,3)的话,那么就要乘以1000。
而除法的基数,则是根据小数后要保留多少个0来决定,比如要保留2个0,则除以100













