SQL Server中SUM()默认用int作中间结果类型,易因累加超2147483647而溢出;正确做法是先CAST列或表达式为bigint/decimal再聚合,避免外部CAST或隐式转换。

为什么SUM()会突然报int溢出错误
不是数据本身超了,是SQL Server在聚合时默认用int做中间结果类型。哪怕所有源字段都是smallint或tinyint,SUM()也会先升到int——而一旦累加值超过2147483647,就直接崩。
典型现象:单条记录查没问题,一加SUM()就抛Arithmetic overflow error converting expression to data type int;或者分页查前几页正常,后面页数变多后溢出。
- 别信“数据看着不大”——10万行每行2万,总和就是20亿,已超
int上限 -
COUNT()不会溢出,但SUM()、AVG()、STDEV()都可能 - 隐式转换会让问题更隐蔽:比如
SUM(col * 1.0)会先算col * 1.0为decimal(13,1),再求和,类型链变长,更容易踩中精度膨胀边界
CAST必须作用在SUM内部,而不是外面包一层
写CAST(SUM(col) AS bigint)是错的——SUM(col)已经按int算完了,溢出发生在CAST之前。
正确做法是让参与聚合的每个值先转成大类型,再求和:
- 源列是整数:
SUM(CAST(col AS bigint)) - 源列是小数:
SUM(CAST(col AS decimal(18,2))),别用float,精度不可控 - 带运算的表达式:
SUM(CAST(col1 AS bigint) * CAST(col2 AS bigint)),每个操作数单独CAST - 如果只是防溢出不关心小数,用
bigint最省心;要保留精度,必须明确decimal(p,s)的p(总位数)足够容纳最大可能和
存储过程中参数和变量类型不匹配是隐形炸弹
错误常不在SQL里,而在上层:比如存储过程声明@amount money,然后写SELECT SUM(@amount * 100)——@amount * 100会被隐式提升为decimal(19,4),后续再进SUM(),类型链拉得更长。
- 参数/变量声明尽量用
decimal,不用money(四舍五入行为不透明)或float(精度丢失) - 变量类型必须和目标字段一致:要插进
DECIMAL(12,2),就声明DECLARE @val DECIMAL(12,2),别图省事写DECIMAL(18,6) - 从表查出的数据赋给变量前,显式
CAST:SELECT @val = CAST(sum_col AS DECIMAL(12,2)) FROM ...
上线前必须验证中间类型,不能只看数值
光看SELECT MAX(col)没用,得确认SQL Server实际算出来的类型。用TYPE_NAME()和PRECISION()查中间结果:
SELECT TYPE_NAME(SUM(CAST(XSCL_XSCL AS bigint))) AS sum_type, PRECISION(SUM(CAST(XSCL_XSCL AS bigint))) AS sum_precision FROM MES_XSCL
复杂聚合建议拆开调试:先查SELECT TOP 1000 CAST(XSCL_XSCL AS bigint) AS c FROM MES_XSCL,确认单列转型没问题,再加SUM。
真正容易被忽略的是——溢出可能发生在应用层JDBC取值时。比如MyBatis用rs.getInt("XSCL_XSCL")去取一个实际是bigint的和,即使SQL没报错,Java端也会抛SQLServerException。这时候得同步改Mapper里的类型映射。










