根本原因在于浮点数无法精确表示十进制小数,导致分组键判等错误和sum累加误差;唯一根治法是建表时用decimal类型,聚合前强制cast转换,而非依赖round事后修正。

直接用 DECIMAL 类型替代浮点字段,是唯一能从根源上解决分组求和误差的办法;ROUND() 只能掩盖显示问题,无法修复已发生的计算漂移。
为什么 GROUP BY + SUM() 会算错?
根本不是函数的问题,而是浮点数在二进制中本就不能精确表示十进制小数。比如 0.1 存进 FLOAT 列后实际是 0.10000000149011612,分组时两个逻辑相等的值(如 19.995 和 19.994999999999999)经 IEEE 754 表示后可能被判定为不同键,导致本该合并的行被拆开;SUM 过程中误差还会逐行累加。
-
WHERE price = 19.99可能永远不命中——因为存进去的就不是精确的19.99 -
GROUP BY ROUND(price, 2)看似分组两位小数,实则把19.995和20.004都 round 成20.00,强行合并不该同组的数据 - MySQL 的
ROUND(19.995, 2)返回20.00,PostgreSQL 可能返回19.99(银行家舍入),跨库迁移时统计口径直接对不上
必须在聚合前转 DECIMAL,不能只在最后 ROUND
ROUND(SUM(price), 2) 是无效补救:SUM 已用浮点完成,误差早已固化;而 SUM(CAST(price AS DECIMAL(10,2))) 是正确路径——每行先转定点数,再累加,全程避开浮点运算链。
- 建表时就该定义:
price DECIMAL(10,2),而不是事后补救 - 已有
FLOAT表迁移:先ALTER TABLE t MODIFY price DECIMAL(10,2),再UPDATE t SET price = ROUND(price, 2)清洗存量 - 若无法改表结构,查询中必须显式前置转换:
SUM(CAST(price AS DECIMAL(10,2))) OVER (),不能等到窗口函数输出后再ROUND() - 注意 MySQL 严格模式下,
CAST溢出会报错;非严格模式则静默截断,务必确认配置
GROUP BY 分组键本身不能依赖浮点计算
用 ROUND(price, 2) 或 price * 1.1 这类表达式直接当分组依据,风险极高。浮点误差会让语义相同的值在分组阶段就被判为不同键。
- 想按“价格区间”分组,用
FLOOR(price * 100) / 100.0(截断)比ROUND(price, 2)更稳定 - 更清晰的做法是用
CASE WHEN显式定义区间:CASE WHEN price BETWEEN 0 AND 99.99 THEN '0-99.99' ... END - SQL Server 不支持在
GROUP BY中直接写表达式,必须先在SELECT中定义别名,再在GROUP BY引用该别名 - 所有数据库中,
GROUP BY后的字段必须与SELECT中非聚合字段完全一致,别名不可跨用
最易被忽略的一点:应用层 ORM 或中间件如果把 DECIMAL 字段当成字符串或 double 绑定,会触发隐式类型转换,让整套精度控制失效。上线前必须查日志,确认实际传入 SQL 的参数类型是 DECIMAL,而不是被框架悄悄转成了 double。











