不能直接用sum()算日终结余,因日终结余需基于上日终余额滚动计算,严格按时间序处理冲正、撤销等操作,并校验交易状态与成对关系,而非简单累加当日amount。

为什么不能直接用 SUM() 算日终结余?
银行交易系统里,日终结余不是简单把当天所有 amount 加起来——它必须基于期初余额滚动计算,且要严格按交易时间顺序处理冲正、撤销、冻结等特殊类型。直接 SUM(amount) 会漏掉状态过滤(比如 status = 'success')、忽略冲正对原始交易的抵消关系,更无法反映同一账户内多笔并发操作的时序依赖。
用 ROW_NUMBER() + 窗口累加模拟记账流水
核心思路是:先按账户+日期+时间排序生成唯一序号,再用窗口函数逐行累加,确保每笔都作用在前一笔结果上。关键点在于初始值必须来自上一日终余额表,而不是硬编码 0。
- 先查出每个账户的上日终余额:
SELECT account_id, closing_balance AS prev_day_bal FROM daily_closing WHERE biz_date = '2024-06-19' - 拼接当日有效交易(排除
status IN ('pending', 'failed', 'reversed'))并排序:ORDER BY account_id, trans_time - 用
SUM(amount) OVER (PARTITION BY account_id ORDER BY trans_time ROWS UNBOUNDED PRECEDING)计算滚动余额,但起始值需和prev_day_bal关联——通常用LEFT JOIN或LATERAL(PostgreSQL)/APPLY(SQL Server)实现
NOT EXISTS 校验冲正是否成对出现
日终结余出错常因冲正单孤立存在:比如原交易已入账,但冲正没执行或执行失败。这时仅靠累加会高估余额。必须显式校验每笔 trans_type = 'reversal' 是否有对应原交易(original_ref_id 存在且状态合法)。
- 典型错误写法:
WHERE trans_type != 'reversal'—— 这直接丢弃冲正,导致余额虚高 - 正确做法:用
NOT EXISTS子查询检查原交易是否存在且为status = 'success',若不存在,则该冲正应被标记为异常,不参与余额计算 - 示例条件:
AND NOT EXISTS (SELECT 1 FROM txns t2 WHERE t2.txn_id = t1.original_ref_id AND t2.status = 'success')
Oracle/MySQL/PostgreSQL 在窗口函数上的兼容性坑
同一个累加逻辑,在不同数据库落地时行为不一致。最常踩的坑是 ROWS UNBOUNDED PRECEDING 在 MySQL 8.0+ 才完全支持,而 Oracle 的 SUM() OVER 默认包含当前行,但某些旧驱动解析时会误判空值处理方式。
- MySQL 5.7 不支持窗口函数 → 必须改用变量模拟:
@bal := @bal + amount,但需确保ORDER BY在变量赋值前生效(加SELECT包裹) - PostgreSQL 中
NULL参与累加会令整行结果变NULL,务必用COALESCE(amount, 0) - Oracle 对
PARTITION BY字段为空值的分组行为和 PostgreSQL 不同,建议提前NVL(account_id, -1)统一空值标识











