同比计算应优先使用DATE_SUB或INTERVAL进行日期偏移,避免字符串拼接;需确保业务周期对齐(如用period_id而非年月加减),并用CASE WHEN处理分母为0或NULL的情况。
用 DATE_SUB 或 INTERVAL 做同比日期偏移,别硬写字符串拼接
同比就是“今年某月 vs 去年同月”,核心是把当前日期减去 1 年。MySQL 里最稳的方式是
,PostgreSQL 用
。硬拼
看似直观,但遇到 2 月 29 日或月末(如 3 月 31 日 → 2 月 29 日不存在)会直接报错或结果错乱。
实操建议:
优先用数据库原生日期运算函数,不是字符串操作
如果字段是
类型,偏移后注意时区和精度,必要时用
截断时间部分
在 WHERE 条件里做偏移时,确保该列有索引,否则
无法走索引
自关联聚合算同比增长率:ON 条件必须对齐业务周期
常见错误是写成
——这只能对齐“自然月”,但实际业务可能按财年、滚动 30 天、或销售周期(比如 4-4-5 周历)。真正要对齐的是“同一业务周期标识”,比如你有一张
表带
和
字段,那关联条件应该是
。
实操建议:
不要依赖年份/月份字段直接加减,先确认业务上“同比”定义是否和日历一致
若无维度表,至少用
(MySQL)对齐周粒度,避免跨周偏差
LEFT JOIN 后记得用
处理去年无数据的情况,否则除零或 NULL 会让整行消失
计算公式里分母为 0 或 NULL 的坑必须显式处理
同比增长率公式是
。一旦
是 0 或 NULL,结果就是
或报错(如 PostgreSQL 的 division by zero)。这不是数学问题,是 SQL 执行逻辑问题。
实操建议:
永远用
别信
——它只解决除零,不解决 NULL 分母;
更可控
如果业务要求“去年为 0 时显示 100%”,那就明确写
窗口函数替代自关联?小心数据膨胀和排序依赖
有人想用
算同比,但 LAG 默认是“上一行”,不是“去年同期”。除非你提前把数据按年月补全(比如用
或递归 CTE),否则 LAG 拿到的大概率是上个月,不是去年同月。
实操建议:
LAG/LEAD 适合环比,不适合同比;强行用就得先保证每期数据严格存在、且排序字段能唯一映射到“去年同周期”
如果真要用窗口函数,得配合
再做二次匹配,复杂度远超自关联
大表慎用自关联+窗口混用,容易触发临时表磁盘溢出,尤其在 MySQL 8.0 以下
同比看着简单,实际卡点全在“周期对齐”和“空值防御”上。业务口径没对齐,代码写得再漂亮,结果也是错的;分母没兜底,报表一跑就断。
DATE_SUB(curdate(), INTERVAL 1 YEAR)current_date - INTERVAL '1 year'CONCAT(YEAR(date_col)-1, '-', MONTH(date_col), '-', DAY(date_col))datetimeDATE()DATE_SUB(sale_date, INTERVAL 1 YEAR)ON t1.month = t2.month AND t1.year = t2.year + 1dim_periodperiod_idis_currentON t1.period_id = t2.last_year_period_idYEARWEEK(sale_date, 1)COALESCE(t2.amount, 0)(this_year - last_year) / last_yearlast_yearNULLCASE WHEN COALESCE(last_year, 0) = 0 THEN NULL ELSE (this_year - last_year) / last_year ENDNULLIF(last_year, 0)COALESCEWHEN last_year = 0 AND this_year > 0 THEN 1.0LAG(amount) OVER (PARTITION BY product_id ORDER BY sale_month)generate_seriesROW_NUMBER() OVER (PARTITION BY product_id, YEAR(sale_date) ORDER BY sale_date)