LAG函数无法直接获取上月数据,因其按行序而非自然月偏移;需先用generate_series或递归CTE补全连续月份,再对归一化月字段开窗计算。

LAG 函数为什么拿不到上个月数据
直接用 LAG 按行偏移,不等于按「自然月」偏移。它只看当前结果集的排序顺序,如果数据本身没覆盖全量月份、或存在空缺(比如某月无记录),LAG 就会跳到上一条物理行——可能是上上周,甚至去年12月。
- 必须先确保查询结果已按年月完整分组并强制补全(如用
generate_series或日期维表) -
LAG的第二个参数默认是 1,别误以为能自动识别“上个月”;它只认排序后的位置差 - 时间字段要用
DATE_TRUNC('month', order_date)统一归到月初,否则同月多天数据会导致重复分组
PostgreSQL 中 LAG 配合日期生成补全上月值
PostgreSQL 用户最容易踩的坑:直接 LAG(value) OVER (ORDER BY date),但原始表只有销售日志,没有“2024-03-01”这样的标准月锚点。
- 先用
generate_series构建连续月序列:SELECT DATE_TRUNC('month', d)::DATE AS month_start FROM generate_series('2024-01-01'::DATE, '2024-06-01'::DATE, '1 month') AS d - 再 LEFT JOIN 销售汇总表,确保每行都有对应月的
sales_amount(空则为 NULL) - 最后在完整月序列上开窗:
LAG(sales_amount) OVER (ORDER BY month_start)
MySQL 8.0+ 没有 generate_series 怎么办
MySQL 不能动态生成日期序列,硬凑 LAG 容易漏月。更稳的方式是用自关联或递归 CTE 模拟月维度,但要注意性能边界。
- 用
WITH RECURSIVE从最大业务月往前推 12 个月:WITH RECURSIVE months AS ( SELECT MAX(DATE_TRUNC('month', created_at)) AS m FROM orders UNION ALL SELECT m - INTERVAL 1 MONTH FROM months WHERE m > '2023-01-01' ) - 别在子查询里直接套
LAG——窗口函数不能嵌套在 CTE 定义中,得在外层 SELECT 再计算 - 如果表数据量大且没按月建索引,
DATE_TRUNC('month', created_at)会触发全表扫描;建议加函数索引:CREATE INDEX idx_orders_month ON orders ((DATE_TRUNC('month', created_at)))
SQL Server 的 LAG 和 EOMONTH 配合要点
SQL Server 没有原生 DATE_TRUNC,用 DATEFROMPARTS(YEAR(d), MONTH(d), 1) 或更稳妥的 EOMONTH(d, -1) + 1 算每月1号。
-
LAG的第三个参数可设默认值,比如LAG(sales, 1, 0) OVER (ORDER BY month_key),避免 NULL 干扰同比计算 - 排序字段
month_key必须是确定性表达式,别用CONVERT(VARCHAR, d, 112)——字符串排序 “202401” - 如果源表含时区字段(如
datetimeoffset),先用SWITCHOFFSET归一,否则跨月计算可能因夏令时偏移半拍
实际写的时候,最常被忽略的是:LAG 只负责“取上一行”,它不管那行是不是你要的“上个月”。补全月维度和对齐时间粒度,才是对比成立的前提。

















