直接用LAG()算同比会出错,因为LAG按物理行偏移而非日期匹配,遇缺失日期(如停业日)将取错行;正确做法是先构造去年同期日期再关联或聚合。

为什么直接用 LAG() 算同比会出错?
因为同比需要比对「去年同期」,不是简单往前推 1 行。如果数据里有缺失日期(比如某门店某天没营业),LAG(order_date, 365) 会取错行——它按物理行数偏移,不校验日期是否真为 365 天前。必须先用 DATE_SUB() 或 INTERVAL 构造目标日期,再关联匹配。
- 正确思路:对每条记录生成
last_year_date = DATE_SUB(order_date, INTERVAL 1 YEAR),再用LEFT JOIN或窗口内MAX() FILTER(PostgreSQL)找对应日的销售额 - MySQL 8.0+ 可用
LAG()配合ORDER BY order_date RANGE BETWEEN INTERVAL 1 YEAR PRECEDING AND INTERVAL 1 YEAR PRECEDING,但兼容性差,多数人写错 - 更稳的做法:先用
ROW_NUMBER() OVER (PARTITION BY store_id ORDER BY order_date)标序号,再自连接ON a.store_id = b.store_id AND DATE_SUB(a.order_date, INTERVAL 1 YEAR) = b.order_date
如何用 OVER(PARTITION BY store_id ORDER BY order_date) 同时算同环比?
窗口函数不能跨分区“跳着看”,所以同环比必须分两步:环比用同一分区内的相邻行,同比需跳出当前 PARTITION BY store_id 的限制——本质是两个不同粒度的匹配逻辑。
- 环比:直接
LAG(sales_amt, 1) OVER (PARTITION BY store_id ORDER BY order_date),安全可靠 - 同比:必须先让数据带上“去年今日”字段,再通过
JOIN或SUM(CASE WHEN ... THEN sales_amt END) OVER (PARTITION BY store_id)聚合,不能只靠单个OVER子句完成 - 别在同一个
OVER里混用ROWS BETWEEN和日期计算,MySQL 不支持RANGE按日期偏移,会静默退化为ROWS
遇到 NULL 值导致同比/环比计算中断怎么办?
LAG() 和 JOIN 在缺数据时返回 NULL,但除法或减法遇到 NULL 就全变成 NULL——这不是 bug,是 SQL 三值逻辑的必然结果,必须显式处理。
- 用
COALESCE(LAG(sales_amt) OVER (...), 0)把空值转 0,但注意:0 和“无数据”语义不同,报表中需加注释 - 更合理的是用
NULLIF(denominator, 0)防止除零,再套一层COALESCE(..., 0)统一输出 - 如果某门店去年今日确实没数据,强行填 0 会扭曲同比率,建议保留 NULL 并在应用层渲染为 “—” 或 “N/A”
为什么按周/月聚合后算同环比反而更准?
日粒度数据受节假日、天气、单日异常订单干扰太大;而周/月维度天然平滑了噪声,且“去年同期”更容易对齐(比如 2024 第 15 周 ≈ 2023 第 15 周),不用纠结 365/366 天偏差。
- 先用
YEARWEEK(order_date, 1)(MySQL)或EXTRACT(YEAR FROM order_date) * 100 + EXTRACT(WEEK FROM order_date)(PostgreSQL)生成周键 - 再
GROUP BY store_id, week_key汇总销售额,之后用LAG(sales_sum, 1) OVER (PARTITION BY store_id ORDER BY week_key)算周环比 - 同比直接
LAG(sales_sum, 52) OVER (PARTITION BY store_id ORDER BY week_key)—— 这里 52 是安全值,因每年约 52 周,比硬写 365 更鲁棒
真正麻烦的不是函数怎么写,而是确认“同比”的业务定义:是自然年同比?财年同比?还是滚动 365 天?一旦定义模糊,所有 DATE_SUB 和 LAG 都在算错东西。

















