正确写法是:ROW_NUMBER()或RANK()必须配合PARTITION BY user_id和精确到秒的ORDER BY created_at(含二级排序如event_type),并用FIRST_VALUE/LAST_VALUE打标首尾行为,且LAST_VALUE需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。

窗口函数怎么写才能正确排序购买路径
转化率计算的前提是路径顺序必须严格按时间对齐,ROW_NUMBER() 或 RANK() 用错就会让“浏览→加购→下单”变成乱序。关键不是选哪个函数,而是 ORDER BY 必须包含精确到秒的时间字段(比如 created_at),且所有用户行为必须在同一个分区里处理。
- 别用
event_time字段名直接排序——如果它只是日期(无时分秒),同一日多次行为会随机排序,导致路径错乱 - 用户级路径必须用
PARTITION BY user_id,漏掉就变成全量混排,转化率完全失真 - 如果存在同一毫秒内多个事件,要补充二级排序,例如
ORDER BY created_at, event_type(把'click'排在'cart_add'前)
如何用窗口函数标记每个用户的首尾行为
转化率本质是“从第一步走到最后一步的人数占比”,所以得先识别每个用户的路径起点和终点。不能靠 MIN()/MAX() 聚合后关联,那样会丢失中间步骤;必须用窗口函数打标。
- 用
FIRST_VALUE(event_type) OVER (PARTITION BY user_id ORDER BY created_at)标出每个用户的首行为(通常是'view') - 用
LAST_VALUE(event_type) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)标出末行为(注意必须加ROWS子句,否则默认只看当前行及之前) - 如果某用户只有
'view'没有'pay',他的末行为就是'view',这类人天然计入分母但不进分子
转化率分母为什么不能直接 count(user_id)
分母不是总用户数,而是完成路径起点的用户数。比如你定义路径为 view → cart_add → pay,那只有触发过 view 的人才算入分母——没浏览就下单的用户属于异常流量,硬算进去会拉低转化率。
- 先过滤出所有起点行为:
WHERE event_type = 'view',再对user_id去重计数,这才是真实分母 - 分子是同时满足起点+终点的用户:需用
EXISTS或JOIN关联该用户是否后续有pay行为,不能只查末行为等于'pay'(因为可能view → pay → refund,末行为是'refund') - 窗口函数本身不解决分子分母聚合逻辑,它只帮你组织好每行归属,最终还得套一层
GROUP BY或子查询
MySQL 8.0 和 PostgreSQL 的兼容性坑
LAST_VALUE() 在 MySQL 8.0 默认行为和 PostgreSQL 不一致:MySQL 需显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,而 PostgreSQL 默认就是全窗口;但如果你在 MySQL 里漏写,结果只返回当前行值,看起来像全为 NULL。
- PostgreSQL 支持
FRAME clause更灵活,但 MySQL 8.0.2+ 才完整支持,低于此版本会报错 - SQL Server 的
LAG()/LEAD()对路径断点检测更方便(比如查用户是否跳过了cart_add),但 MySQL 不支持IGNORE NULLS,遇到空值容易中断链路 - 别依赖
WINDOW命名复用——MySQL 8.0 支持,PostgreSQL 支持,但某些 BI 工具解析器不认,建议直接展开写
真正卡住人的往往不是函数语法,而是路径定义本身:你认定的“标准路径”是否覆盖了真实用户行为?比如“微信小程序直接支付”绕过了 view,这种流量要不要剔除?得先跟业务对齐路径口径,再写 SQL。

















