业务年度指企业自定义的财年周期(如4月1日至次年3月31日),不能直接用year()函数,因其按自然年划分,会导致跨自然年的业务年度数据被错误拆分;需先通过date_sub与if逻辑计算business_year,再按business_year和month排序后用lag()实现同比,且须嵌套聚合与窗口函数。

什么是业务年度,为什么不能直接用 YEAR()?
业务年度不是自然年,比如某公司财年从每年4月1日到次年3月31日。直接用 YEAR(order_date) 会把2024-04-01和2024-03-01归到不同年份,但它们实际属于同一业务年度(2024财年)。必须先将日期映射到对应业务年度,再分组计算。
- 用
DATE_SUB(order_date, INTERVAL (MONTH(order_date) 把4月及以后的日期归到当前年,3月及以前归到上一年(假设财年起始为4月) - 更稳妥的做法是定义一个辅助列:
business_year=YEAR(DATE_SUB(order_date, INTERVAL IF(MONTH(order_date) - 注意:MySQL 8.0+ 支持
IF(),旧版本需改用CASE WHEN
同比计算必须用 LAG(),但怎么对齐业务年度?
LAG() 默认按物理顺序取前一行,如果数据没按业务年度+月份排序,结果完全错乱。必须显式排序,且排序字段要包含完整时间粒度。
- 排序必须用:
ORDER BY business_year, MONTH(order_date)(不能只用business_year) - 同比值 = 当前期值 / 上一期(即上年同月)值 − 1,所以要用
LAG(sum_amount, 1),而不是LAG(sum_amount)(后者默认偏移1,但明确写更安全) - 如果某月数据缺失(比如2023-04有数,2024-04没数),
LAG()返回NULL,除法会得NULL—— 需加COALESCE(lag_value, 0)或过滤掉空值
窗口函数里不能直接用 GROUP BY,怎么先聚合再同比?
窗口函数在聚合后执行,所以必须两层:外层窗口、内层聚合。常见错误是试图在同一个 SELECT 里既 SUM() 又用 LAG() 而不嵌套。
- 正确结构是子查询或 CTE:
WITH monthly AS ( SELECT YEAR(DATE_SUB(order_date, INTERVAL IF(MONTH(order_date)
- 关键点:
PARTITION BY month_num确保只跟“同月”的上年对比;NULLIF(..., 0)防止除零 - 不要用
PARTITION BY business_year,那会把同一年内不同月分组,毫无意义
为什么结果里2023年首月总是 NULL?
因为 LAG() 对每个分区第一行返回 NULL —— 比如2023-04是4月分区里的首行,没有“2022-04”数据可拉。这不是 bug,是设计如此。
- 如果表里确实缺2022-04数据,那就真没同比值;如果数据齐全但还是
NULL,检查business_year计算是否把2022-04错标成2023年 - 业务系统常把“首年”设为基准年,不展示同比,这是合理预期
- 若强行补0,用
COALESCE(LAG(...), 0),但注意:除以0仍报错,得配合NULLIF一起用
业务年度映射和 PARTITION BY 的粒度选择,是同比分析里最容易漏掉的两个硬约束。少一个,整个结果就不可信。











