未结清发票的动态账龄是以当前查询时刻为基准,对每张status≠'paid'或balance>0的发票实时计算逾期天数,并按天数分段(如0-30天)进行金额汇总。

什么是未结清发票的动态账龄
动态账龄不是固定按开票日算N天,而是以「当前查询时刻」为基准,对每张未结清发票实时计算已逾期天数(或距今天数)。关键在于:只统计 status != 'paid' 或 balance > 0 的记录,且不能用 GROUP BY 聚合掉明细行——否则无法逐行算账龄。
SUM OVER 本身不直接算账龄,但能配合账龄分段做滚动汇总
SUM() OVER 是窗口函数,擅长累计、分区聚合,但它不生成天数。真正算账龄得靠 DATEDIFF(MySQL/SQL Server)或 current_date - invoice_date(PostgreSQL),再用 CASE 分段归类。而 SUM() OVER 的作用是:在每个账龄区间内,对金额做累计求和,比如“0-30天未结清总额”“31-60天未结清总额”的滚动合计。
- 错误做法:
SUM(balance) OVER (ORDER BY days_overdue)—— 这会把所有账龄混在一起累加,失去区间语义 - 正确做法:先用
CASE WHEN把每行标上账龄段(如'0-30'),再按该字段PARTITION BY算小计,或用ORDER BY做升序累计 - 注意:PostgreSQL 中日期相减得转
INTEGER才能用于CASE判断;SQL Server 的DATEDIFF(day, ...)直接返回整数
完整可执行示例(PostgreSQL)
SELECT
invoice_id,
invoice_date,
balance,
current_date - invoice_date AS days_since,
CASE
WHEN current_date - invoice_date BETWEEN 0 AND 30 THEN '0-30'
WHEN current_date - invoice_date BETWEEN 31 AND 60 THEN '31-60'
WHEN current_date - invoice_date BETWEEN 61 AND 90 THEN '61-90'
ELSE '90+'
END AS aging_bucket,
SUM(balance) OVER (PARTITION BY
CASE
WHEN current_date - invoice_date BETWEEN 0 AND 30 THEN '0-30'
WHEN current_date - invoice_date BETWEEN 31 AND 60 THEN '31-60'
WHEN current_date - invoice_date BETWEEN 61 AND 90 THEN '61-90'
ELSE '90+'
END
) AS bucket_total,
SUM(balance) OVER (
ORDER BY current_date - invoice_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_by_days
FROM invoices
WHERE balance > 0;
这里用了两个 SUM() OVER:第一个按账龄段汇总(PARTITION BY),第二个按天数升序做累计(ORDER BY ... ROWS)。务必加 WHERE balance > 0 过滤未结清项——漏掉这句,已结清发票也会参与计算,结果完全失真。
容易被忽略的兼容性与性能点
MySQL 8.0+ 支持窗口函数,但 current_date - invoice_date 在 MySQL 中返回的是“天数差”,类型是 DECIMAL,CASE 中比较需小心隐式转换;SQL Server 更建议用 DATEDIFF(day, invoice_date, GETDATE()) 避免时区歧义。
- 索引建议:在
balance和invoice_date上建复合索引,加速WHERE balance > 0+ 账龄计算 - 避免在
OVER()的ORDER BY里用表达式(如current_date - invoice_date)作为排序键——某些旧版引擎可能不走索引 - 如果发票表超百万行,把账龄逻辑下推到 CTE 或物化视图里预计算
days_overdue字段,比每次查都算快得多
动态账龄的核心不在“SUM OVER”,而在“每行独立判断 + 精准过滤 + 合理分区”。窗口函数只是帮你把判断后的结果组织成业务需要的汇总形态。










