LAG()和LEAD()必须配合PARTITION BY user_id与ORDER BY event_time才能精准锚定用户行为时序,否则全局计算会导致归因错误;首末触点归因需结合ROW_NUMBER()、MAX(CASE WHEN...)及显式ROWS帧定义,MySQL尚不支持IGNORE NULLS需用FIRST_VALUE+COALESCE模拟。

窗口函数本身不归因,但能精准锚定归因逻辑所需的时序节点——比如“用户成交前最后一次有效触点”或“首次来源渠道”,这才是电商、金融、广告场景中真正要归因的点。
LAG() 和 LEAD() 必须配合 PARTITION BY + ORDER BY 才能正确打行为序列标
直接对全表用 LAG(event_time) 会把不同用户的事件混在一起算时间差,结果完全不可信。关键不是函数本身,而是分组和排序是否贴合业务路径。
- 必须写成
LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time),否则返回的是全局上一行,不是该用户上一次行为 - 判断“浏览→加购”跃迁,用
LAG(event_type) OVER (PARTITION BY user_id ORDER BY event_time)比查时间差更稳定 - 大表慎用多层嵌套
LAG(),建议先用WHERE event_time >= CURRENT_DATE - INTERVAL '30 days'过滤活跃用户再计算
ROW_NUMBER() + MAX() OVER 是首触点/末触点归因的核心组合
归因口径不同,窗口写法差异极大。首触点要取最早一次曝光,末触点要取成交前最后一次付费行为——不能靠简单 JOIN,得靠窗口把时间锚点“带下来”再匹配。
- 首触点:用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time)标记首次曝光,再关联该用户首笔订单(注意别丢掉无行为记录的纯搜索成交) - 末触点:用
MAX(event_time) FILTER (WHERE channel != 'organic') OVER (PARTITION BY user_id)找最后付费渠道时间,再匹配最接近且早于订单时间的行为 - MySQL 不支持
FILTER,得改用MAX(CASE WHEN channel != 'organic' THEN event_time END) OVER (PARTITION BY user_id)
LAST_VALUE() 默认帧范围导致结果错乱,必须显式指定并处理 NULL
很多人发现 LAST_VALUE(channel) 总返回当前行自己的值,或一堆 NULL——根本原因是默认窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,且没忽略空值。
- 必须写成
LAST_VALUE(channel) IGNORE NULLS OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - PostgreSQL / BigQuery 支持
IGNORE NULLS;Spark SQL 需用COALESCE(LAST_VALUE(col IGNORE NULLS), FIRST_VALUE(col)) - MySQL(截至2026年5月)仍不支持
IGNORE NULLS,得用FIRST_VALUE()+COALESCE()组合模拟
最容易被跳过的点是:没在窗口定义里显式声明 ROWS 而非依赖默认 RANGE,尤其当排序字段存在重复(如秒级时间戳)时,RANGE 会合并行,导致“最新值”实际不是最新那条。

















