阶梯电费需按累计用电量动态分段计价,PostgreSQL可通过SUM() OVER与LAG结合LATERAL切片匹配档位,精确计算每度电对应费率,关键在边界处理、默认值设定及排序字段业务语义明确。

阶梯电费的核心是:同一用户不同用电量区间适用不同单价,且需按累计用量动态判断所属档位。PostgreSQL 的窗口函数本身不直接“分段计价”,但结合 LAG、SUM() OVER() 和条件累积逻辑,可以干净地实现逐段拆解与加权求和。
用 SUM() OVER (ORDER BY) 累计用电量并划分档位
关键不是对单条记录算价格,而是先还原出每度电“落在哪个档位”。需将原始账单按时间/序号排序,用窗口累计得到截至当前的总用电量,再与阶梯阈值比对。
- 假设表
usage_records有字段:user_id、record_time、kwh(本次抄表增量) - 阶梯规则存在另一张表
tariff_tiers:tier_start、tier_end、rate_per_kwh - 必须先用
SUM(kwh) OVER (PARTITION BY user_id ORDER BY record_time)得到每个记录对应的累计用量cum_kwh - 注意:累计值代表“到本次为止总共用了多少”,所以某条记录的
kwh实际覆盖的是cum_kwh - LAG(cum_kwh, 1, 0) OVER (...)这一段区间
用 JOIN LATERAL 或子查询匹配每度电所属档位
累计值只能告诉你“当前总用量在哪个档”,但阶梯计费要精确到“这 100 度电中,前 50 度属第一档、后 50 度属第二档”。必须把每条记录的用电量切片。
- 推荐用
LATERAL关联tariff_tiers,对每条usage_records计算它与各档位的重叠量: - 重叠起点 =
GREATEST(tier_start, LAG(cum_kwh, 1, 0) OVER (...)) - 重叠终点 =
LEAST(tier_end, cum_kwh) - 有效重叠量 =
GREATEST(0, 重叠终点 - 重叠起点) - 这样每条原始记录可能生成多行(每档一行),再乘以对应
rate_per_kwh即得分段电费
避免常见错误:阈值开闭区间与累计逻辑错位
最常踩的坑是档位边界处理错误,导致某度电被漏计或重复计费。
- 确认
tariff_tiers中的tier_start/tier_end是左闭右开(如 [0, 200)、[200, 400))还是左闭右闭([0, 200]、[201, 400])——这直接影响GREATEST/LEAST的写法 -
LAG(cum_kwh, 1, 0)必须显式指定默认值0,否则首条记录的LAG为NULL,导致整个重叠计算失效 - 不要在 WHERE 中提前过滤档位;切片逻辑必须在 JOIN 或 LATERAL 中完成,否则会丢失跨档记录
- 如果账单含退补电量(负值
kwh),累计逻辑需额外处理,SUM() OVER仍可用,但档位匹配时重叠计算要兼容负向区间
真正难的不是写对一个查询,而是让这套逻辑在用户持续新增用电记录时依然稳定输出可审计的分段结果——累计窗口的排序字段必须业务语义明确(比如不能用自增 ID 代替抄表时间),且档位表变更后需评估历史账单是否需要重算。

















