应改用DECIMAL类型或显式CAST转定点数,因浮点数二进制精度差异导致JOIN等值匹配失败,ROUND()仅做数值舍入不改变类型与精度缺陷。

直接改用 DECIMAL 类型,或者在 JOIN ON 条件中显式 CAST 转为定点数——ROUND() 不能解决浮点匹配失效问题,它只是数值变换,不改变底层二进制精度缺陷。
为什么 JOIN 后浮点数总匹配不上
根本不是数据“看起来一样”,而是两个表里存的 price 字段实际二进制值不同:比如一个存的是 19.990000000000002,另一个是 19.989999999999998。数据库在 ON 条件里直接拿原始浮点值做等值比较,误差哪怕只有 1e-15,结果就是 FALSE。
常见错误现象:
-
SELECT * FROM a JOIN b ON a.price = b.price返回空,但你知道两边都有19.99 - 用
WHERE a.price = 19.99查不到数据,但CAST(a.price AS DECIMAL(10,2)) = 19.99就能查到 - 在 PostgreSQL 中执行
SELECT a.price, b.price, a.price = b.price,明明显示都是19.99,最后一列却是false
为什么 ROUND(price, 2) 在 ON 里无效
ROUND() 不是类型转换函数,它返回的仍是浮点类型(如 FLOAT 或 DOUBLE PRECISION),只是对数值做了舍入。关键问题是:数据库优化器通常不会把 ROUND() 的结果物化为新列,而是在每次比较时重新计算、再比对原始浮点值。
更麻烦的是跨库行为不一致:
- SQLite 的
ROUND(19.99, 2)返回整数20,拿去跟19.99比永远false - SQL Server 中
ROUND(price, 2)若输入是float,输出仍是float,不是decimal - MySQL 5.7+ 默认银行家舍入,PostgreSQL 也是,但旧版本可能用传统四舍五入,导致两边舍入结果不一致
所以别写 ON ROUND(a.price, 2) = ROUND(b.price, 2) —— 它既不可靠,也无法走索引。
真正能落地的三种方案
必须让关联键变成确定性、可索引、无精度扰动的值:
-
首选:源头改用
DECIMAL——建表时就把金额类字段定义为DECIMAL(10,2),插入前由应用层用字符串解析(如 Python 的Decimal('19.99'))。这样JOIN天然稳定,还能走索引 -
临时补救:显式
CAST转定点数——在ON中统一转成同精度DECIMAL:CAST(a.price AS DECIMAL(10,2)) = CAST(b.price AS DECIMAL(10,2))(PostgreSQL / SQL Server)
MySQL 请用CONVERT(a.price, DECIMAL(10,2)) = CONVERT(b.price, DECIMAL(10,2)) -
兜底策略:容忍微小误差——不用等值,改用范围判断:
ABS(a.price - b.price) 。但该写法无法利用索引,仅适合小数据量或离线核对
容易被忽略的字段对齐陷阱
即使你用了 CAST,如果两表字段本身类型/精度/编码不一致,仍会触发隐式转换,导致索引失效或匹配偏差:
- 一个表是
FLOAT,另一个是REAL,虽然都是浮点,但某些数据库不认为它们“可安全隐式转换” - 一个
DECIMAL(8,2)和一个DECIMAL(10,2)关联,虽属“弱对齐”,但如果前者出现溢出(如存了999999.99),后者可能截断或报错 - CTE 或子查询里写了
ROUND(price, 2),外层JOIN时仍可能被重算——别假设中间结果已“固化”精度
最常踩的坑是:线上跑得好好的 SQL,一换数据库(比如从 MySQL 迁到 PostgreSQL)或加个 CTE,就突然匹配失败。问题往往不出在逻辑,而出在类型对齐细节没对齐。











