SQLite sum 与 total:空表返回不同,大整数求和也不能随意互换
没有记录时,汇总值该表示什么
统计表在多数日期都有数据,偶尔某天没有记录,SUM 却返回 NULL,让调用方的加法报错。把它改成 TOTAL 看起来能直接获得零,但这还改变了返回类型和大整数计算方式。空值约定与数值范围是两件事,不能只修复空表样本就认为两者可以随处互换。
下面在 Python 3.12.14、SQLite 3.53.1 的内存数据库中检查四种输入:空表、全是 NULL、小整数和大整数。保存为 demo.py,运行 python demo.py。脚本只操作自己创建的临时表,整数溢出是有意触发并捕获的演示,所有预期行为都通过断言检查。
同时看值与 Python 接收到的类型
每个案例都先清空这张内存表,再写入明确样本。SUM 和 TOTAL 都忽略 NULL 输入,但没有任何非空值时,前者给 NULL,后者给浮点零。为了避免屏幕上的八掩盖类型差异,小整数样本还用 type 检查 SUM 是 int、TOTAL 是 float。
越界样本把有符号六十四位整数最大值与一相加。代码分别执行 SUM(x) 和 COALESCE(SUM(x), 0),证明空值替换并不能接住聚合内部的计算错误。最后使用仍处于整数范围、却超过双精度连续整数精确区间的数,观察 TOTAL 丢掉一个单位的情况。
AI概念示意图:空容器对应不同返回约定,有限容器外溢则提醒检查整数范围。图片只说明概念,不是运行截图。
import sqlite3
con = sqlite3.connect(":memory:")
try:
con.execute("CREATE TABLE samples(x INTEGER)")
def put(values):
con.execute("DELETE FROM samples")
con.executemany("INSERT INTO samples VALUES (?)", [(x,) for x in values])
for label, values in [("empty", []), ("all null", [None, None])]:
put(values)
row = con.execute("SELECT SUM(x), TOTAL(x) FROM samples").fetchone()
assert row == (None, 0.0)
print(label + ":", row)
put([3, None, 5])
row = con.execute("SELECT SUM(x), TOTAL(x) FROM samples").fetchone()
assert row == (8, 8.0)
assert type(row[0]) is int and type(row[1]) is float
print("small:", row)
put([(1 << 63) - 1, 1])
failures = 0
for sql in [
"SELECT SUM(x) FROM samples",
"SELECT COALESCE(SUM(x), 0) FROM samples",
]:
try:
con.execute(sql).fetchone()
except sqlite3.OperationalError as exc:
assert "integer overflow" in str(exc)
failures += 1
else:
raise AssertionError("integer overflow expected")
assert failures == 2
total = con.execute("SELECT TOTAL(x) FROM samples").fetchone()[0]
assert isinstance(total, float)
print("overflow cases:", failures, "total:", total)
put([1 << 53, 1])
exact, approximate = con.execute("SELECT SUM(x), TOTAL(x) FROM samples").fetchone()
assert exact == 9007199254740993
assert approximate == 9007199254740992.0
assert exact != approximate
print("precision:", exact, approximate)
finally:
con.close()每次修正只解决一种问题
empty 和 all null 都打印 (None, 0.0)。这说明仅凭 SUM 的空结果无法区分没有行与有行但全部未知;若业务需要区分,应同时检查行数和非空值数量。small 打印 (8, 8.0),值相等却类型不同,之后序列化或传给要求整数的接口时可能体现差异。
overflow cases 为二,表示普通 SUM 与外层 COALESCE 都按预期报 integer overflow。COALESCE 处理已经算出的 NULL,不会把失败的计算变成有效值。TOTAL 在同一组输入上可以返回浮点数,但这不是它已经具备任意精度整数能力的证明。
precision 一行把整数 9007199254740993 与浮点 9007199254740992.0 放在一起。差一个单位在计数场景中就是实际数据损失;把后者再转 int 也无法找回原来的一。示例的 Python 比较明确断言它们不相等,避免格式化显示掩盖精度问题。
让结果类型服从业务含义
若整数汇总范围已知且不会溢出,只想把无非空输入时的 NULL 显示为零,可以有意识地使用 COALESCE(SUM(x), 0)。但如果 NULL 代表数值尚未采集,替换为零本身也会改变业务含义,应在展示或接口约定里说明,不要把未知数量当作确实没有。
需要近似实数求和时,TOTAL 的浮点结果可能符合要求;需要精确大整数时,应另选能满足范围的表示和汇总方案。TOTAL 不抛整数溢出也不代表数值范围无限,极端浮点输入还有无穷值等问题,须另外测试。本文没有安装 SQLite 扩展,也没有测试其他数据库,不能从一次成功运行推断金额或科学计算中的误差已被控制。
验收汇总查询至少保留四类样本:正常值、无值、范围边界和精度边界。先决定允许哪种结果类型,再选择聚合函数与空值处理。这样报表变得能运行的同时,计数含义、异常路径和数值精度也都有可检查的依据。


