AVG对INT列计算时直接走整数除法导致小数丢失,正确做法是在除法前将至少一个操作数转为浮点或高精度类型,如AVG(CAST(score AS DECIMAL(10,2)))或AVG(score * 1.0)。

AVG对INT列计算时直接走整数除法
不是显示问题,是计算过程就丢小数。比如 score 是 INT 类型,AVG(score) 在 SQL Server 和旧版 PostgreSQL 中会先算总和(仍是 INT),再用整数除法除以行数——商向零截断,余数扔掉。80、90、99 三值平均本该是 89.666…,结果直接变成 89。
MySQL 行为稍异:默认返回 DECIMAL,但精度可能不足(如只保留 1 位小数),看起来像“四舍五入”,其实是底层精度不够导致的截断。
- 查证方式:
SELECT AVG(score), CAST(AVG(score) AS CHAR) FROM students;看原始字符串,避开客户端渲染干扰 - 别信
ROUND(AVG(score), 2)能救回来——输入已经是整数,ROUND 没东西可四舍五入 - 只要列定义是
TINYINT、SMALLINT、INT,这个陷阱就存在
怎么让AVG返回带小数的正确值?
核心动作只有一个:在除法发生前,把至少一个操作数变成浮点或高精度类型。不能靠事后 ROUND,必须干预计算链起点。
- 推荐写法:
AVG(CAST(score AS DECIMAL(10,2)))—— 显式控制精度,报表场景最稳 - 简洁写法:
AVG(score * 1.0)—— 依赖隐式转换,MySQL/PostgreSQL 都认,但score * 1.0结果是FLOAT,有浮点误差风险(比如 0.1 + 0.2 ≠ 0.3) - SQL Server 可用:
AVG(CONVERT(DECIMAL(10,2), score)),效果等价于CAST - 别写
CAST(AVG(score) AS DECIMAL(5,2))—— 先算出低精度 AVG 再转,已经丢了小数
GROUP BY + AVG时小数位为什么飘忽不定?
AVG() 对数值列的结果类型由输入列决定,但小数位数不固定。INT 列的 AVG 可能返回 6~15 位小数,FLOAT 列可能带浮点误差,DECIMAL 列则按规则扩展精度(如 DECIMAL(10,2) 的 AVG 可能返回 DECIMAL(14,6))。
- 导出到 Excel 或前端展示时看到整数,大概率是客户端自动省略小数位,不是数据错了
- 做条件判断(如
AVG(score) > 85.5)时,多余小数不影响逻辑,但可读性差 - 统一做法:
ROUND(AVG(score), 2)—— 先算均值再四舍五入,主流数据库都支持,语义清晰 - 避免用
CAST(AVG(score) AS DECIMAL(5,2))替代ROUND:MySQL 截断、PostgreSQL 四舍五入、SQL Server 行为不一致,跨库迁移容易翻车
窗口函数AVG() OVER()也踩整数除法坑
AVG(score) OVER() 看似广播全局均值,但如果 score 是 INT,某些引擎仍按整数除法算——结果还是整数,后续减法、比较全受影响。
- 必须写成:
AVG(CAST(score AS DECIMAL(10,2))) OVER()或AVG(score * 1.0) OVER() - 别加
ORDER BY:写成AVG(score) OVER(ORDER BY id)就变成累积平均,不是你要的全局值 - WHERE 已生效:
AVG(score) OVER()算的是 WHERE 过滤后的均值,不是原始全表 - 差值列要防浮点毛刺:
ROUND(sales - AVG(CAST(sales AS DECIMAL(10,2))) OVER(), 2),否则可能出 -0.0000001 这种干扰值
真正麻烦的不是写法本身,而是这个错误不报错、不警告,只静默返回偏低的整数。上线后发现“平均单价”集体少了几毛、“完成率”全为 0%,排查时得从数据类型、计算链、客户端渲染三层往回推——多敲两个字符 CAST(... AS DECIMAL),比三天后翻日志强得多。











