
本文详解如何利用sql窗口函数(如sum() over)按时间顺序动态计算员工往来结算的期初余额、变动金额与期末余额,生成清晰的流水式对账报表。
本文详解如何利用sql窗口函数(如sum() over)按时间顺序动态计算员工往来结算的期初余额、变动金额与期末余额,生成清晰的流水式对账报表。
在员工往来结算管理中,仅知道每笔交易的金额(amount)并不足以反映资金状态的变化过程;真正关键的是呈现“每一笔发生后,账户余额如何演进”。这要求我们按时间顺序(created_at)逐行计算:期初余额 = 上一笔期末余额,变动金额 = 当前交易额,期末余额 = 期初余额 + 变动金额。
核心难点在于:原始数据是离散的交易记录,而我们需要的是带累积逻辑的流水视图。直接使用 GROUP BY id 并嵌套 LAG(SUM(...)) 会导致语义错误——SUM() 在分组后已失去单行意义,且 LAG() 无法跨聚合层级正确引用前序累计值。
✅ 正确解法是避免提前分组,改用窗口函数进行有序累积计算。以下为兼容 MySQL 8.0+、PostgreSQL、SQL Server 等主流数据库的标准写法:
SELECT
employee_id,
COALESCE(
SUM(amount) OVER (
PARTITION BY employee_id
ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
), 0) AS start_balance,
amount AS change,
SUM(amount) OVER (
PARTITION BY employee_id
ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS final_balance,
reason,
created_at
FROM settlement_settlement
WHERE employee_id = 101 -- 指定员工ID
ORDER BY created_at;? 关键说明:
-
PARTITION BY employee_id确保余额计算严格隔离不同员工,避免交叉干扰; -
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING精确获取“当前行之前所有行”的累计和,作为期初余额; -
COALESCE(..., 0)将首笔交易的期初余额安全设为0; -
ORDER BY created_at是窗口函数生效的前提,务必确保该字段无重复或含唯一性约束(必要时可追加id作为第二排序键)。
⚠️ 注意事项:
- 若
created_at存在毫秒级重复,建议添加主键id到ORDER BY子句(如ORDER BY created_at, id),保证排序确定性; - 旧版 MySQL(
- 实际生产中,
start_balance和final_balance应为DECIMAL(p,s)类型(如DECIMAL(12,2)),避免浮点数精度丢失。
通过该方案,您将获得结构清晰、逻辑可验证的结算流水,不仅满足对账需求,也为后续审计追踪与BI可视化提供坚实的数据基础。

















