“连续三天增长”指日期相邻且字段值严格递增的三个自然日,需用LAG获取前两日值并验证日期差为1及严格大于关系,结果返回第三天日期。

什么是“连续三天增长”的业务含义
这个需求表面是时间序列分析,实际核心是定义清楚“增长”和“连续”:
- “增长”指某字段(比如
sales或user_count)相比前一天严格变大,不是大于等于; - “连续三天”指日期上相邻的三个自然日(如 2024-05-01、05-02、05-03),不能跳过周末或缺失日期——如果原始数据本身缺某天,那天就不能参与连续判断;
- 多数人踩的第一个坑:直接用
LAG()比较后简单加WHERE过滤,却没处理“三段增长必须首尾连贯”这一逻辑。
用窗口函数 + 自连接构造连续三日增长链
推荐用 LAG() 分别取前1天、前2天的值,再在同一条记录里验证三者关系:
SELECT date, sales
FROM (
SELECT
date,
sales,
LAG(sales, 1) OVER (ORDER BY date) AS prev1_sales,
LAG(sales, 2) OVER (ORDER BY date) AS prev2_sales,
LAG(date, 1) OVER (ORDER BY date) AS prev1_date,
LAG(date, 2) OVER (ORDER BY date) AS prev2_date
FROM daily_metrics
) t
WHERE
-- 确保日期连续:当前日 - 前1日 = 1天,前1日 - 前2日 = 1天
DATEDIFF(date, prev1_date) = 1
AND DATEDIFF(prev1_date, prev2_date) = 1
-- 确保严格递增
AND sales > prev1_sales
AND prev1_sales > prev2_sales;
注意点:
-
DATEDIFF在 MySQL 中是DATEDIFF(a,b)返回 a−b 的天数;PostgreSQL 要写(a::date - b::date);SQL Server 用DATEDIFF(day, b, a) - 如果表里日期不唯一(比如一天多条记录),必须先按日期聚合(
GROUP BY date),否则LAG()行为不可控 -
ORDER BY date必须存在且确定,否则窗口结果无意义
当数据稀疏(缺日期)时,用生成日期序列补全
如果原始表本身缺少某些日期(比如节假日没数据),上面的方法会漏掉本应成立的连续段。此时需先生成完整日期序列,再左连接原始数据:
WITH date_series AS (
SELECT generate_series(
(SELECT MIN(date) FROM daily_metrics),
(SELECT MAX(date) FROM daily_metrics),
'1 day'::interval
)::date AS date
),
filled AS (
SELECT ds.date, COALESCE(dm.sales, 0) AS sales
FROM date_series ds
LEFT JOIN daily_metrics dm ON ds.date = dm.date
),
lagged AS (
SELECT *,
LAG(sales) OVER (ORDER BY date) AS s1,
LAG(sales, 2) OVER (ORDER BY date) AS s2,
LAG(date) OVER (ORDER BY date) AS d1,
LAG(date, 2) OVER (ORDER BY date) AS d2
FROM filled
)
SELECT date FROM lagged
WHERE
date - d1 = 1 AND d1 - d2 = 1
AND sales > s1 AND s1 > s2;
关键差异:
-
generate_series是 PostgreSQL 函数;MySQL 8.0+ 可用递归 CTE 模拟;SQL Server 用master..spt_values或递归 CTE -
COALESCE(dm.sales, 0)的 0 是占位值,但要注意:若业务中 0 表示“无数据”,则不能填 0,得填NULL并在后续条件中排除该行
性能与边界情况提醒
- 数据量大时,
ORDER BY date 必须有索引,否则窗口函数会全表排序,极慢
- 如果“增长”定义为“比前一日高 5% 以上”,把
sales > s1 改成 sales > s1 <em> 1.05</em> 即可,但注意浮点精度和 NULL 传播(NULL 1.05 还是 NULL)
- 最容易被忽略的一点:连续增长的“第三天”才是结果行,不是第一天——所以最终 SELECT 的是
date(即最高值那天),而不是起始日。很多人查出来后发现日期对不上,就是混淆了锚点。
ORDER BY date 必须有索引,否则窗口函数会全表排序,极慢 sales > s1 改成 sales > s1 <em> 1.05</em> 即可,但注意浮点精度和 NULL 传播(NULL 1.05 还是 NULL) date(即最高值那天),而不是起始日。很多人查出来后发现日期对不上,就是混淆了锚点。

















