核心思路是用LAG()获取前两月销售额,判断sales > prev_sales > prev2_sales是否连续成立;需确保月份严格连续(补空月或用ROW_NUMBER验证),并对外层聚合标记结果以识别存在连续增长的门店。

用窗口函数计算月度销售额变化趋势
核心思路是先按门店、月份聚合销售额,再用 LAG() 获取前一个月的值,算出环比增长(正数即增长)。关键不是“有没有增长”,而是“连续三个月都增长”——这需要判断当前月、上月、上上月三者是否都满足 current > previous > previous2。
常见错误是只比较相邻两个月,漏掉“连续性”验证;或者用自连接写三层 JOIN,逻辑臃肿且易出错。
- 必须按
store_id和sale_month(建议用DATE_TRUNC('month', order_date)或TO_CHAR(order_date, 'YYYY-MM')统一格式)分组聚合 -
LAG(sales, 1) OVER (PARTITION BY store_id ORDER BY sale_month)拿上月,LAG(sales, 2)拿上上月 - 注意:月份必须严格连续(如 2024-01、2024-02、2024-03),跳月(如缺 2024-02)会导致
LAG取到更早的值,结果失真
过滤连续三个月正向增长的记录
拿到每行的 sales、prev_sales、prev2_sales 后,不能直接 WHERE sales > prev_sales AND prev_sales > prev2_sales —— 这样返回的是“第三个月”,但你需要的是“该门店存在这样一段连续三个月”,所以得在外层再聚合或去重。
更稳妥的做法是给每个满足条件的月份打标记,再按门店统计出现次数:
SELECT store_id
FROM (
SELECT store_id,
CASE
WHEN sales > prev_sales AND prev_sales > prev2_sales
THEN 1 ELSE 0 END AS is_continuously_up
FROM monthly_sales_with_lag
) t
GROUP BY store_id
HAVING SUM(is_continuously_up) >= 1;
这个 HAVING SUM(...) >= 1 表示只要存在至少一个“第三个月”满足连续增长,就入选。如果要求“必须恰好连续三个月且仅此一段”,需额外加时间范围约束。
处理数据稀疏和边界情况
真实业务中,门店可能某月没销售,导致月份不连续。此时 LAG 会跨过空月取值,破坏“连续”语义。例如:2024-01(10万)、2024-03(12万)、2024-04(15万),LAG(sales,1) 在 2024-04 会取 2024-03 的值,但 2024-02 缺失,实际不构成“连续三个月”。
- 方案一:先生成门店+全量月份的骨架表(用
GENERATE_SERIES或递归 CTE),LEFT JOIN 销售数据,空月填 0 或 NULL,再做LAG - 方案二:用
ROW_NUMBER() OVER (PARTITION BY store_id ORDER BY sale_month)给月份编号,要求编号差为 2(即rn = rn-1 = rn-2 + 2),确保物理连续 - NULL 值要显式排除:
WHERE sales IS NOT NULL AND prev_sales IS NOT NULL AND prev2_sales IS NOT NULL
PostgreSQL 和 MySQL 8.0+ 写法基本一致,但低版本 MySQL 需模拟 LAG
MySQL 5.7 或更早不支持窗口函数,只能用相关子查询或变量变量模拟 LAG,性能差且易出错。例如用变量:
SELECT store_id, sale_month, sales,
@prev1 AS prev_sales,
@prev2 AS prev2_sales,
@prev2 := @prev1,
@prev1 := sales
FROM (SELECT @prev1 := NULL, @prev2 := NULL) init,
(SELECT store_id, sale_month, SUM(amount) AS sales
FROM orders
GROUP BY store_id, sale_month
ORDER BY store_id, sale_month) t;
但变量顺序依赖执行计划,MySQL 8.0+ 强烈建议直接用标准 LAG()。SQL Server、Oracle 同理,语法微调即可(如 SQL Server 用 DATEFROMPARTS(YEAR(date), MONTH(date), 1) 截月)。
真正难的不是写出来,而是确认“连续三个月”在业务里到底指日历月连续,还是有销售的最近三个月——前者要补空月,后者可直接用 ROW_NUMBER 排序。这点不明确,结果一定错。

















