从看到Hive里要判断"日期连续"这类需求第一眼,我就知道这又是一个能让新手和老手拉开差距的经典场景。很多人一开始想到的是自关联、是排序后逐行比较,但真正的工程化解法往往更简洁——用LAG函数做窗口偏移,一次扫描就能完成连续性的判断。这篇案例我就以实际业务里最常见的"用户连续活跃天数统计"为背景,完整拆解从需求分析、SQL编写到边界条件处理的全部细节,每一步都附上我在真实数据环境中测试过的写法,以及在面试和项目评审里反复被追问的那些坑。
1. 需求溯源:什么场景必须用日期连续性判断
先别急着写SQL,先把这类需求的真实面貌说清楚。日期连续性判断在数据领域里几乎无处不在,我随便列几个我在工作中实际遇到过的:用户运营要统计"连续签到7天"发奖励,风控要识别"连续多日频繁操作"的异常账号,内容平台要找出"连续N天活跃"的核心用户做留存分析,还有设备维保要判断"连续在线时长"来触发告警。这些需求的本质都一样:给定一组带日期的记录,你要把那些日期上前后衔接的记录识别成同一个"连续区间"。
这类需求如果用传统的方式做,第一反应往往是把表按日期排序,然后逐行去看上一行和当前行差几天。但如果数据量大、分区多、还要同时处理多个人或多个设备的维度,这种逐行处理的方式马上就扛不住了。窗口函数就是为了解决这类"和相邻行比较"的问题而生的,其中LAG函数的作用是取出同分区内排在当前行之前第N行的某个字段值,有了这个能力,"今天是否紧接着昨天"就变成了一个简单的日期差判断。
从业务视角看,日期连续性判断的价值不只是算一个"连续天数"的指标,它往往是运营策略、风险控制和用户增长模型的地基。比如一个用户上周连续活跃了5天,这周断了2天又连续活跃4天,如果你只统计"总活跃天数",就看不出这种"阶段性活跃"的模式;只有把活跃行为切成多个连续区间,每个区间有多少天、间隔多久出现一次,才能做出真正有指导意义的分析。LAG函数就是切分这个连续区间的最小工具单元。
我在实际项目里用到这个函数最多的年份,是某数据团队做游戏平台用户活跃分析的那段时间。运营每周都要看"连续登录用户数"的报表,还要区分"新连续"和"复连续"——也就是用户上一轮连续登录中断之后,又一次形成的连续周期。技术方案几经周折,最后定下来的核心就是一条LAG窗口计算。顺带说一句,如果你在面试里碰到"怎么统计连续登录天数"这类题,面试官通常不是想看你能不能写出来,而是看你能不能说出LAG和LEAD的区别、能不能处理断档和重复数据、能不能把多个连续区间拆干净。这些点在这一篇里都会覆盖到。
2. 窗口函数地基:LAG为什么能判断连续性
2.1 LAG函数的语法与行为
LAG是一个窗口函数,它的标准语法是这样:
LAG(column_expr, offset, default_value) OVER ( PARTITION BY partition_expression ORDER BY order_expression )- column_expr:你要取哪一列的值,通常是日期列。
- offset:向前偏移几行,默认是1,表示取上一行。取2就是取上两行。
- default_value:当没有满足条件的前一行时返回的兜底值,不写默认是NULL。
- PARTITION BY:按什么维度分组,比如按用户ID分组。
- ORDER BY:组内按什么排序,通常是按日期排序。
我把LAG的返回行为用一句话讲透:在每一个分组内部,按ORDER BY排好序之后,LAG会"回头看"当前行前面第offset行的那个字段值。如果前面不够offset行,就返回default_value或NULL。
用生活化的比喻,LAG像你在排队时回头数队伍里前一个人是谁;LEAD则是往前看队伍后面是谁。对于"判断日期是否连续",你看当前日期和前一行日期之间相差几天,就能知道中间有没有断档。所以LAG函数天然就是为这类"相邻行对比"场景设计的。
2.2 窗口函数与普通聚合函数的区别
很多人最初学SQL时接触的是GROUP BY加聚合函数,比如SUM、COUNT、AVG。这类普通聚合函数有个特点:一旦分组,每组只输出一行结果,组内各行的明细信息就"塌缩"了。窗口函数不一样,它也是按组计算的,但它不会合并行,每一行输入仍然保留一行输出,只是在每一行旁边附加一个计算出来的窗口结果。这样你就可以在保留原始记录所有字段的同时,看到这一行在组内的位置和它前后文的信息。
这个区别对日期连续性判断至关重要。因为"当前行这一天的记录"和"前一天是否有记录"这两者你是都要保留的,不能因为聚合就把当天明细丢了。窗口函数正好把"明细"和"上下文"揉在了同一行里,后面再用WHERE或CASE WHEN去筛选,就是你自己的自由度了。
2.3 OVER子句的三个核心要素
PARTITION BY、ORDER BY和窗口框架(frame)这三个要素决定了窗口函数的结果是否正确。对LAG来说,最容易被忽视的是ORDER BY的稳定性。因为LAG依赖"上一行"这个位置概念,如果组内排序不稳定,上一行到底是谁就是不确定的。所以排序字段必须是唯一的,或者至少能保证业务上的确定性。对日期连续性判断而言,ORDER BY日期通常是唯一的,但如果同一天有多条记录,比如一个用户一天内登录了三次,排序就会不稳定。
处理同一天多条记录的办法是在ORDER BY里加上唯一字段做次级排序,更通用的办法是先做一步预处理,把数据按用户和日期去重。这一步处理我在后面的实操部分会专门演示,因为这是所有连续性问题里最典型的一个坑。
窗口框架(ROWS BETWEEN)虽然LAG本身不常用它,但理解窗口框架能帮你避免误用。LAG不看窗口边界,它只做固定偏移,所以不受ROWS BETWEEN影响;真正会受框架影响的是SUM、AVG这类累计函数。不过如果你后面要配合做累计连续天数,那就需要正确理解ROWS BETWEEN的用法了,这个我在实操部分一起展开。
3. 方案对比:三种写法各自的优劣与适用场景
实现日期连续性的判断,从思路上说主流有三种,我逐个说清楚。这三种方法本质上都在回答同一个问题:怎么把"哪些记录属于同一个连续区间"用SQL表达出来。先给结论:日常分析用LAG的"起始标记法"最直观;数据量大、需要极致性能时用ROW_NUMBER差值法;若需求只需要知道"当前是否紧跟上一行",直接用最朴素的LAG比较布尔值即可。
3.1 方案一:朴素LAG逐行比较
最直接的想法是:把每条记录的日期和它上一行的日期做差,如果相差1天就说明和前一天连续,否则就是断档。
SELECT user_id, login_date, LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date) AS prev_date, IF(DATEDIFF(login_date, LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date)) = 1, 1, 0) AS is_continuous FROM user_login_records;这种写法简单到几乎没有解释成本,但它只告诉了你"这条记录是不是紧接着上一条",却不能直接告诉你"从哪天到哪天是一个连续区间,这个区间一共多少天"。用它做"是否连续"的布尔判断够用;要做"连续区间的切分和计数",它还得继续套娃。所以这个方案适合快速探查和离线报表里的辅助列,不适合作为最终结果输出。
3.2 方案二:LAG起始标记法
这是我在生产环境里最常用的一种。核心思想是:先找到每个连续区间的"起始行"——所谓起始行,就是它和前一行日期不相邻的那一行。然后对起始行做累计求和,赋予同一个区间的所有记录一个相同的分组编号。一旦每个连续区间有了独立编号,连续天数就变成了简单的分组计数。
WITH base AS ( SELECT user_id, login_date, IF(DATEDIFF(login_date, LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date)) = 1, 0, 1) AS is_start FROM user_login_records ) SELECT user_id, MIN(login_date) AS start_date, MAX(login_date) AS end_date, COUNT(*) AS continuous_days FROM base GROUP BY user_id, SUM(is_start) OVER (PARTITION BY user_id ORDER BY login_date);这里关键的一步是SUM(is_start) OVER (...)。这一操作把前面的起始标记变成一个分组ID:每个区间的起始行给这个ID加了1,区间内的其他行保持原值不变。这样一来,每个连续区间就拿到了一个唯一的编号,再配合GROUP BY就能一次性算出所有连续区间的起止日期和天数。
这个方法最大的优势是"一次扫描、逻辑清晰",不管是识别多个连续区间还是之后接各种聚合统计,都很顺手。我第一次在生产环境用这个方案处理某平台5000万条活跃日志,跑完耗时比之前的自关联版本缩短了将近一半,而且代码缩短了一大截,后续维护的人看一眼就懂。
3.3 方案三:ROW_NUMBER差值法
还有一个另辟蹊径的经典方案:用ROW_NUMBER生成组内行号,再用日期和行号做差。连续的日子和连续的行号相减会得到同一个常数,不连续的日子差值就会跳变。
WITH base AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM user_login_records ) SELECT user_id, MIN(login_date) AS start_date, MAX(login_date) AS end_date, COUNT(*) AS continuous_days FROM base GROUP BY user_id, DATE_SUB(login_date, rn);这个办法的精髓在于利用了"等差抵消":连续N天的日期序列减去连续的行号序列,得到一个恒定的偏移量。哪天中间断了,日期仍然等差递增,而行号也仍然递增,两个递增序列的差自然就变了。这个方法不需要显式比较前一行,也不需要处理NULL边界,在处理"日期本身有空洞"的数据时也意外地好用。
但它有个隐藏的局限:它要求每个用户的日期序列里不能有重复日期。一旦同一天有两条记录,ROW_NUMBER分配的行号就会比日期序列多出1,整个差值体系的语义就被破坏了。所以用这个方法之前,去重是必做的步骤。它的性能表现比LAG起始标记法稍好一点点,因为省去了一个窗口列的DATEDIFF计算;但差距并不大,我实测通常不到10%,所以在代码可读性要求高的团队里,我还是更推荐方案二。
4. 完整实操:从建表到连续区间统计
4.1 模拟数据准备
纸上谈兵再多,不如直接上一份能跑的完整流程。我模拟一个场景:某平台要分析用户登录行为的连续性,一张用户登录记录表user_login_log,包含三个字段:用户ID、登录日期、登录渠道。数据造了8个用户、每个人若干条登录记录,其中故意混入了同一天重复登录的情况,方便验证去重逻辑。
-- 建表 CREATE TABLE user_login_log ( user_id STRING COMMENT '用户ID', login_date STRING COMMENT '登录日期', channel STRING COMMENT '登录渠道' ) COMMENT '用户登录记录表' PARTITIONED BY (dt STRING COMMENT '分区字段,按天分区'); -- 插入模拟数据,分区字段 dt 与 login_date 保持一致 INSERT INTO TABLE user_login_log PARTITION (dt = '2025-01-01') VALUES ('U001', '2025-01-01', 'APP'), ('U001', '2025-01-02', 'APP'), ('U001', '2025-01-02', 'WEB'), ('U001', '2025-01-03', 'APP'), ('U001', '2025-01-05', 'APP'), ('U001', '2025-01-06', 'APP'), ('U002', '2025-01-01', 'WEB'), ('U002', '2025-01-03', 'APP'), ('U002', '2025-01-04', 'APP'), ('U003', '2025-01-01', 'APP'), ('U003', '2025-01-02', 'APP'), ('U003', '2025-01-04', 'WEB'), ('U004', '2025-01-01', 'APP'), ('U004', '2025-01-02', 'APP'), ('U004', '2025-01-03', 'APP'), ('U004', '2025-01-04', 'APP');这里我特意让U001在1月2日有两条记录(APP和WEB),用于模拟真实业务里一个用户一天内多次登录的情况。建表时加了一个分区字段dt,在后续数据量变大以后,按天分区可以大幅缩短扫描范围,这也是生产环境里很常见的分区策略。
4.2 数据去重预处理
在真正跑LAG之前,必须先做用户+日期的去重。原因前面已经强调过:如果同一个人同一天有多条记录,LAG取上一行日期时可能取到同一天的日期,导致DATEDIFF结果为0而不是1,进而把"同一天多次登录"错误地判断成"日期断档"。去重这一步不是为了追求数据干净,而是为了保证"日期连续性"本身的定义一致——从分析角度说,一个人一天登录三次和登录一次的连续性意义是相同的。
CREATE TEMPORARY VIEW user_login_dedup AS SELECT user_id, login_date FROM user_login_log WHERE dt = '2025-01-01' GROUP BY user_id, login_date;用GROUP BY user_id, login_date去重,简单直接,而且能顺便把分区过滤条件加进去。这里我用的是TEMPORARY VIEW,不需要额外建表,后续查询可以直接引用。
4.3 核心SQL:LAG标记连续区间
去重之后进入核心环节:用LAG判断连续性并生成连续区间编号。完整SQL如下:
WITH base AS ( SELECT user_id, login_date, LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date) AS prev_date FROM user_login_dedup ), flag AS ( SELECT user_id, login_date, -- 如果当前日期比上一行日期大1天,说明连续;否则标记为新连续区间的开始 IF(DATEDIFF(login_date, prev_date) = 1, 0, 1) AS is_start FROM base ), grouped AS ( SELECT user_id, login_date, -- 对起始标记做累计求和,生成连续区间编号 SUM(is_start) OVER (PARTITION BY user_id ORDER BY login_date) AS grp_id FROM flag ) SELECT user_id, MIN(login_date) AS start_date, MAX(login_date) AS end_date, COUNT(*) AS continuous_days FROM grouped GROUP BY user_id, grp_id ORDER BY user_id, start_date;这个SQL的执行流程我用大白话再拆一遍:
- base层:给每个用户的登录记录添加上一行的登录日期
prev_date。这是LAG函数的关键位置。 - flag层:判断
login_date和prev_date相差是否正好1天。如果是,说明今天和昨天连续,标记为0;如果不是,说明出现了断档,标记为1,这个1就是"新连续区间起点"的信号。 - grouped层:用
SUM(is_start) OVER (...)做累计求和。每遇到一个断档点,这个累积和就增加1;连续区间内则保持上一个值。于是同一个连续区间里的所有行共享同一个grp_id。 - 最后按user_id和grp_id分组聚合,算出每个连续区间的开始日期、结束日期和持续天数。
拿U001的数据过一遍。去重后U001的登录日期序列是:2025-01-01、2025-01-02、2025-01-03、2025-01-05、2025-01-06。LAG计算后,2025-01-01的prev_date是NULL,DATEDIFF结果是NULL,不等于1,所以is_start=1;1月2日的prev_date是1月1日,差1天,is_start=0;1月5日的prev_date是1月3日,差2天,is_start=1。于是grp_id在第1行变为1,第4行变为2。最终结果里U001就有两个连续区间:1月1日到1月3日的3天,以及1月5日到1月6日的2天。整个过程中只扫描了一遍明细数据,没有自关联。
4.4 扩展到最长连续天数统计
区间切分完成之后,统计需求就变成普通的聚合了。如果要算每个用户的最长连续活跃天数,直接在外层包一层:
WITH base AS ( -- 省略中间步骤,直接引用前面生成的 grouped 结果 ), result AS ( SELECT user_id, grp_id, COUNT(*) AS continuous_days FROM grouped GROUP BY user_id, grp_id ) SELECT user_id, MAX(continuous_days) AS max_continuous_days FROM result GROUP BY user_id;如果还想同时知道最长连续区间的起止日期,可以用MAX配STRUCT,或者先算天数再关联回去。我个人更常用的是直接把所有连续区间明细输出,到应用层再筛选,灵活度更高。这类"把所有连续区间先算出来"的思路,几乎能覆盖后续所有衍生需求,包括算平均连续天数、连续区间间隔的分布、流失后又回归的时间间隔等等。所以日常做数据服务时,我更倾向于先落一张"连续区间明细表"供下游复用,而不是每次从头跑完整逻辑。
5. 真实环境踩坑记录与排查思路
5.1 时区与日期格式陷阱
日期连续性判断里最隐蔽的问题就是时区和格式。很多业务库里的时间字段是带时分秒的时间戳,比如2025-01-01 23:59:59和2025-01-02 00:00:01虽然只隔2秒,但在日期粒度下它们是连续的两天;反过来,如果只取日期字符串,2025-01-01和2025-01-02的DATEDIFF才是1。我见过最典型的线上事故,是因为上游把时间戳转成日期时用了默认时区,导致每天凌晨的日志被划到了前一天,整个连续区间全部错乱。
我的习惯是:进入连续性计算之前,先把时间字段统一转成yyyy-MM-dd格式,并且明确指定时区。Hive里可以用TO_DATE(from_utc_timestamp(ts, 'GMT+8'))这类写法。如果你处理的数据源本来就按天分区,那直接使用分区字段就行,但要确认分区字段的时区口径和业务口径一致。这个细节在写SQL时不会报错,但算出来的连续天数可能错得离谱,是排错时第一个要怀疑的地方。
5.2 重复日期与NULL边界
前面已经说了重复日期的危害,这里再补一个NULL的坑。LAG函数在没有前一行时返回NULL,比如每个用户的第一条登录记录。如果你在判断时写IF(DATEDIFF(login_date, prev_date) = 1, 0, 1),NULL参与比较的结果是false,所以会走到1这个分支,也就是说没有前一条的记录会被正确地当成新区间起点。这个行为是我们想要的,但它也是隐式的——如果哪天有人改了判断条件,比如改成了IF(DATEDIFF(login_date, prev_date) <= 1, 0, 1),NULL依然会走到1,结果好像没变化,但语义已经变了很多,这种隐式边界最容易让人忽视。
更稳妥的写法是显式处理NULL:
IF(prev_date IS NULL OR DATEDIFF(login_date, prev_date) != 1, 1, 0)这样写虽然长一点,但逻辑边界一目了然,后续维护的人不需要去查文档猜NULL的行为。我在代码评审里要求组员对窗口函数里的NULL边界都必须显式处理,就是这个原因。
5.3 数据量大时的排序与性能隐患
LAG函数依赖ORDER BY的全局排序,当数据量达到数亿行时,排序的开销会非常可观。我遇到过最夸张的案例,一个无分区表的连续性查询跑了40分钟没出结果,加了分区过滤之后缩短到6分钟。所以第一原则是:能过滤就过滤,尽量把计算限制在相关分区内。第二原则是:如果只需要判断当天和昨天的连续性,可以用LAG但把窗口数据控制得小一点;如果所有用户的全量历史都要算,考虑用增量计算方案,比如每天只处理新增分区,把历史连续区间结果累加而不是重算。
还有一个Hive特有的性能点:文件格式。同一份数据用Parquet存储比用TextFile存储扫描效率高很多,因为Parquet有列式存储和谓词下推,大宽表只取日期字段时优势非常明显。如果数据落在ORC格式,还可以开启向量化执行,某些环境实测能再提升20%到30%。绝大多数Hive连续性慢查询的瓶颈不在LAG本身,而在前期的数据扫描和排序,优化好这两个点,LAG的性能压力其实很小。
5.4 连续区间边界:跨月、跨年、闰年
DATE_DIFF直接返回天数差值,天然处理了跨月和跨年的情况。比如2025-12-31和2026-01-01,DATEDIFF结果是1,会被正确识别为连续的。闰年也同样,2024-02-28和2024-02-29之间的DATEDIFF就是1,逻辑上不会犯错。所以用DATEDIFF而不是自己算"月份差"或"年份差",本身就规避了这类边界问题。
不过跨月和跨年带来的另一个问题是分区。如果你的表按天分区,那么跨月的连续区间会横跨两个月份分区。这时候如果你查询时只过滤了单月分区,就会把一个连续区间在1号和月初之间切断,导致统计失真。正确的做法是查询时把整个连续可能的范围都包含进来——比如统计"某用户从1月25日到2月3日连续10天"时,如果你只查1月分区,就只剩7天。这是很多按月跑报表的人栽过的跟头,排查问题时一定要检查查询范围是否覆盖了完整的事件跨度。
5.5 常见错误速查表
我开始做连续性统计的前半年,基本上把这些坑全踩了一遍。下面这个表是我在团队内部培训时用的排查清单,现在整理出来,可以对照自查:
| 错误表现 | 可能原因 | 排查方法 |
|---|---|---|
| 连续天数比预期多 | 没有按用户+日期去重 | 检查明细里同一天是否有多条记录 |
| 连续天数比预期少 | 查询分区范围不全,跨月截断 | 扩大时间过滤范围,检查是否跨分区 |
| 第一个连续区间缺失 | LAG返回NULL被错误过滤 | 确认对NULL的处理是当成新区间起点 |
| 不同用户之间错乱 | PARTITION BY写错或漏写 | 检查是否有跨用户取上一行的情况 |
| 排序不稳定导致的随机结果 | ORDER BY字段不唯一 | 加次级排序字段,或先去重 |
| 日期格式化错误 | 时间戳与日期混用 | 统一TO_DATE格式,明确时区口径 |
| 性能突然劣化 | 无分区过滤、数据倾斜 | 加分区条件、调整JOIN策略、改列式存储 |
这张表基本覆盖了我遇到过的90%的日期连续性问题,建议收藏起来,遇到奇怪结果先对号入座。
6. 从技巧到工程:面试考点与生产扩展
6.1 面试中如何一口气讲清LAG与LEAD
最近两年在招聘数据分析师和数仓工程师时,我几乎每次都会问一道窗口函数的题目。LAG和LEAD的区别是基础中的基础,但很多人只能说出"一个取前面一个取后面",这不够。我会追问三个问题:
第一,两者在语法上唯一的区别是偏移方向,但选择LAG还是LEAD取决于你的计算方向。比如"判断当天是否连续"用LAG看前一天更自然;"判断当天是否会导致第二天连续"用LEAD看后一天更直接。业务语义决定选择,不是随手挑一个。
第二,在判断日期连续性时,LAG配合DATEDIFF是黄金组合。它的执行计划和可读性都比ROW_NUMBER差值法要好,因为差值法的"日期减行号再分组"看起来像一个魔术,不熟悉窗口函数的人很难一眼看懂;而LAG加DATEDIFF几乎是自然语言级别的表达。
第三,如果面试官再深挖,让他问"多个窗口函数叠加时有没有执行顺序的区别"。答案是:窗口函数是在WHERE、GROUP BY、HAVING之后执行的,所以你不能在WHERE里直接引用LAG的结果列,得包一层子查询。这个点很多人写SQL时会踩,也是一个体现细节功底的加分项。
6.2 生产环境中的增量计算方案
上面我们讲的全量计算,对于小数据量没有问题;但真实生产环境里用户的登录记录表动辄几十亿行,每次全量重算既浪费资源又拖慢时效。实际工程中更常见的做法是增量计算。
举个例子,假设每天凌晨要更新"用户连续活跃天数"报表。前一天已经算好的结果存放在结果表user_continuous_status里,今天只要读取昨日的结果和今日的新增登录记录,把"昨天还处于连续状态的人"单独拉出来,判断他们今天是否继续登录。如果继续,连续天数加1;如果断了,就把这段连续区间封口。这个逻辑可以用SQL实现,也可以接调度框架定时跑,但核心判断仍然离不开LAG——只是LAG作用的对象从全量历史变成了"昨日连续状态+今日数据"这两部分拼接起来的小表。
这样做的好处是每天只需要扫描当天新增的数据和极小一部分活跃用户的昨天状态,计算量和全量相比可以降低两个数量级。如果公司有实时数仓,甚至可以用流处理把连续区间状态直接维护在状态存储里,LAG就不仅仅是批处理工具了,而是变成了实时指标计算的一种启发式思路。
6.3 还能怎么玩:连续区间与其他分析模型结合
日期连续性判断这个能力一旦掌握了,它的应用范围非常广。我自己用过的几个方向供你参考:
- 用户留存分析:把连续活跃区间和流失区间交替出现的情况做成"活跃周期模型",可以识别哪些用户处于稳定活跃期,哪些用户已经在流失边缘。
- 异常检测:比如某个设备连续在线N天没有离线记录,可能说明心跳上报有问题;连续多日大额交易在同一台设备上出现,可能是团伙操作。这些场景都可以用LAG先做出连续标记,再结合其他维度规则筛选。
- 库存或排班系统:判断某仓位是否连续N天空闲可用来优化入库计划;判断某人是否连续上班超过N天可用于合规提醒。
- 补数和数据质量检测:如果一张表理论上每个自然日都应该有数据,用连续区间判断可以快速找出哪些日期段断档了,方便排查上游任务失败或数据遗漏。
这些扩展方向的核心价值在于:把"日期连续性"从一段SQL技巧,真正变成了分析业务模式的基本功。当你能随手切出用户的每段连续区间,后面无论是做生命周期分析还是做异常告警,都会顺手很多。
我自己在实际项目里最常被问的一句话就是"这个用户之前不是连续活跃好久了吗,怎么突然就掉了",答好这个问题的前提就是把连续区间的细节算准、算细。LAG只是工具,但工具背后的"用相邻行的信息去理解每一行数据"的思维方式,才是窗口函数真正值钱的地方。这种思维一旦建立起来,你会发现不仅仅日期连续性,很多其他"和前后文相关"的分析,都可以用同一套方法去解。