
本文详解如何通过窗口函数在 sql 中准确计算单个员工的逐笔互清结算余额变化,生成“期初余额→变动金额→期末余额”三列报表,并修正常见聚合与排序错误。
本文详解如何通过窗口函数在 sql 中准确计算单个员工的逐笔互清结算余额变化,生成“期初余额→变动金额→期末余额”三列报表,并修正常见聚合与排序错误。
在员工互清结算(mutual settlements)场景中,每笔交易代表一笔债权或债务变动(如借款、还款、代垫等),需按时间顺序动态追踪账户余额演进过程。核心目标是:对指定员工(如 employee_id = 101)的所有交易,按 created_at 升序排列,逐行计算——
- Start Balance:该笔交易前的累计余额(即上一笔的 Final Balance,首笔为 0);
-
Change:当前交易的
amount(可正可负); - Final Balance:当前累计余额 = Start Balance + Change。
关键误区在于混淆聚合与窗口计算逻辑。原查询中 GROUP BY id 与 SUM(amount) OVER (...) 混用导致语义冲突:GROUP BY 会压缩多行,而窗口函数需在明细行上运行。正确做法是避免无意义的 GROUP BY,直接在原始明细行上应用窗口函数。
✅ 推荐写法(兼容 MySQL 8.0+、PostgreSQL、SQL Server、Oracle):
SELECT
employee_id,
COALESCE(
SUM(amount) OVER (
ORDER BY created_at, id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
) AS start_balance,
amount AS change,
SUM(amount) OVER (
ORDER BY created_at, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS final_balance
FROM settlement_settlement
WHERE employee_id = 101
ORDER BY created_at, id;? 说明与注意事项:
-
ORDER BY created_at, id:显式添加id作为次级排序键,避免时间相同时的非确定性结果; -
ROWS BETWEEN ... AND 1 PRECEDING精确获取“截至上一行”的累计和,比LAG(SUM(...))更可靠(后者在未聚合时无意义); -
COALESCE(..., 0)将首笔交易的前序和(NULL)转为 0,符合业务起点; -
切勿
GROUP BY id:本场景无需分组聚合,原始记录即为明细交易,分组将破坏窗口函数的行级上下文; - 若使用 MySQL 5.7 或更早版本(不支持窗口函数),需改用自连接或变量模拟,但推荐升级至 8.0+ 以获得标准、可维护的解决方案。
最终输出将严格匹配需求表格结构,清晰呈现余额随时间推移的动态轨迹,为财务对账、员工结算报表提供精准数据支撑。

















