SQLite 的数学能力比你想象的多
SQLite 以精简著称,但它自带了一整套数值函数:四舍五入、绝对值、向上和向下取整、乘方、开方、对数、三角函数、随机数。大部分数学函数是在 SQLite 3.35(2021 年)加入的,所以任何还算新的环境,比如 Python、Node 自带的版本、浏览器里 WebSQL 的前身,或者官方命令行工具,都可以直接使用。
深入之前,先快速看一组示例:
六个函数,一行结果。这一页剩下的部分会逐一介绍每类函数的用途,以及值得知道的坑。
ROUND:你最常用的那个
ROUND(value, digits) 把数值四舍五入到指定的小数位数。第二个参数可以省略,省略时会四舍五入到最接近的整数(但结果仍然是浮点数):
有几点需要注意:
ROUND(3.14159)返回的是3.0,不是3。如果你想要整数,用CAST(ROUND(x) AS INTEGER);如果要直接截断,用CAST(x AS INTEGER)。- SQLite 采用“远离零方向舍入”:
2.5舍入为3,-2.5舍入为-3。有些数据库使用银行家舍入法(四舍六入五成双),SQLite 不是。 digits参数可以是负数:ROUND(1234.5, -2)会舍入到最接近的 100,得到1200。
实际工作中,你写得最多的大概是用来显示金额的 ROUND(price, 2)。
ROUND 和 CAST 不一样
很多人想四舍五入时却用了 CAST(x AS INTEGER),结果就掉进了坑里:
CAST 是向零截断,直接把小数部分扔掉。ROUND 则是四舍五入到最接近的整数。对 2.9 来说,两者相差整整 1。选哪个,取决于你真正想要的行为。
ABS、SIGN 与数的正负
ABS(x) 返回绝对值。SIGN(x) 根据正负返回 -1、0 或 1:
ABS 是最常用的,在“这两个值相差多少”这类查询里很方便。SIGN 用得少一些,但当你想按方向给行分组(支出还是收入、盈利还是亏损),又不想写一个显式的 CASE 时,它很有用。
CEIL、FLOOR 和 TRUNC
这几个函数得到的是整数形式的值,但不是四舍五入。CEIL 总是向上取,FLOOR 总是向下取,TRUNC 总是向零取:
留意负数的情况。FLOOR(-2.9) 是 -3(离零更远),而 TRUNC(-2.9) 是 -2(向零靠近)。处理负数时 FLOOR 和 TRUNC 的结果不同,选错了就是经典的差一错误。
CEILING 是 CEIL 的别名,哪个读起来顺手就用哪个。
真正的坑是整数除法
这不是函数,而是 / 运算符,但它绊倒新手的次数比任何数学函数都多:
当两边都是整数时,SQLite 会做整数除法并截断结果。只要有一边是 REAL,整个表达式就变成实数运算。解决办法是确保至少有一个操作数是浮点数:要么把 2 写成 2.0,要么做类型转换。
引用列的时候最容易中招:total_cents / 100 返回的是整数,total_cents / 100.0 才会返回你真正想要的美元金额。
MOD 和 % 运算符
MOD(x, y) 返回 x / y 的余数。% 运算符做的是同一件事:
MOD(17, 5) 和 17 % 5 都返回 2。在 SQLite 中对零取模会返回 NULL,而不是报错,这和大多数编程语言不一样。如果你在意这一点,就先检查除数,或者把调用包在 CASE WHEN y = 0 THEN ... END 里。
函数写法和运算符写法可以互换。大多数人会用 %,因为更短。
POWER、SQRT、EXP、LOG
用于乘方和开方:
几个容易让人出错的地方:
POW是POWER的别名。- SQLite 中的
LOG(x)以 10 为底。LN(x)是自然对数。带两个参数的LOG(b, x)是以b为底的对数。(这和很多编程语言不同,那些语言里log是自然对数,这里遵循的是 SQL 的惯例。) - 对负数求
SQRT会返回NULL,而不是报错。 - 按照惯例,
POWER(0, 0)返回1。
计算复利、换算成分贝、计算距离,凡是涉及几何或指数运算的地方,这些函数都用得上。
RANDOM 和 RANDOMBLOB
RANDOM() 返回一个有符号 64 位整数,可能落在整个取值范围内的任何位置:
要得到某个范围内的数,先用 ABS 包一层(因为 RANDOM() 是有符号的),再用 %。要得到 0 到 1 之间的实数,就除以 64 位整数的最大值。SQLite 没有返回 0 到 1 之间随机数的内置 RAND(),需要你自己拼出来。
RANDOMBLOB(n) 返回 n 个字节的随机数据,适合用来生成会话令牌或测试数据。配合 HEX() 可以得到可打印的字符串:
每次调用都会产生一个新值。不要指望 RANDOM() 在同一行里两次返回相同的数,即使在同一个表达式里,每次调用也是相互独立的。
综合运用
一个小例子:对一张商品表做几项计算,并把价格舍入到分。
关键在于 price_cents / 100.0:那个 .0 让除法变成实数运算,然后 ROUND 把结果保留到两位小数。没有它,1299 / 100 得到的是 12,而不是 12.99。
下一篇:日期与时间
数值函数负责数学运算,日期和时间则需要一套自己的工具。SQLite 把它们存成文本、实数或整数,并提供了一组小而强大的函数,用来解析、格式化日期时间并做日期运算。这就是接下来的内容。
常见问题
SQLite 中如何保留两位小数?
用 ROUND(value, 2)。第二个参数是要保留的小数位数,ROUND(3.14159, 2) 返回 3.14。只传一个参数时,ROUND(x) 会四舍五入到最接近的整数,但返回的仍是浮点数,这一点常常让人意外。
SQLite 有 CEIL 和 FLOOR 吗?
有,从 SQLite 3.35(2021 年)开始,数学函数就是内置的:CEIL(x)、FLOOR(x)、SQRT(x)、POWER(x, y)、LOG(x)、EXP(x) 等等。在更老的版本上,除非加载了数学扩展,否则用不了这些函数;大多数现代环境(Python、Node、浏览器)都默认启用了它们。
为什么 SQLite 中 5 / 2 返回 2?
因为两个操作数都是整数,SQLite 会做整数除法并截断结果。把其中一边转换成 REAL,比如 5 / 2.0 或 CAST(5 AS REAL) / 2,就能得到 2.5。这不是数值函数的怪癖,而是 / 运算符在整数参数下的行为。