跳出率指单次会话中仅访问一个页面且无任何交互的行为占比,核心是按session_id分组后统计行为数为1的会话比例;需用COUNT(*) OVER(PARTITION BY session_id)计算每会话行为总数,再筛选session_len=1并按渠道聚合。

跳出率的定义必须先对齐
跳出率不是 PostgreSQL 内置概念,得自己定义:单次会话中只访问了 1 个页面(或 1 条行为记录)且无后续交互,即视为跳出。关键点是「按会话分组」+「判断该会话内行为总数是否为 1」。如果直接用 COUNT(*) 而不配合 PARTITION BY session_id,就无法区分会话粒度,算出来的是全局比例,不是渠道维度的跳出率。
ROW_NUMBER() 和 COUNT() OVER 要一起用
只靠 ROW_NUMBER() 排序没用,它不能告诉你这个 session 总共有几条记录;只用 COUNT() OVER (PARTITION BY session_id) 才能拿到每会话总行为数。常见错误是写成 COUNT(*) OVER (PARTITION BY channel)——这算的是每个渠道多少条日志,不是多少个单页会话。
实操建议:
- 先用
COUNT(*) OVER (PARTITION BY session_id)算出每条记录所属会话的总行为数,存为session_len - 再用
WHERE session_len = 1筛出所有跳出会话 - 最后按
channel分组,用COUNT(*) FILTER (WHERE session_len = 1) * 1.0 / COUNT(*)算各渠道跳出率
注意 session_id 的完整性与去重
如果原始表里存在重复日志、测试流量混入、或 session_id 为空/为 NULL,COUNT() OVER (PARTITION BY session_id) 会把 NULL 当作同一组,导致统计失真。常见现象是某渠道跳出率突然飙到 90%+,一查发现几百个 session_id IS NULL 全被算进去了。
安全做法:
- 加
WHERE session_id IS NOT NULL AND session_id != ''预过滤 - 确认
session_id在业务侧是可靠生成的(比如前端带参、后端统一签发),不是靠 IP + User-Agent 拼接的弱标识 - 如果要用 IP + 时间窗口模拟 session,得先用
LAG()或自定义逻辑生成临时session_id,不能直接套窗口函数
性能敏感时避免两层嵌套子查询
有人习惯写两层:外层 GROUP BY channel,内层用 SELECT *, COUNT(*) OVER (...) AS session_len FROM logs。PostgreSQL 会物化中间结果,数据量大时慢得明显。更优解是用 CTE 提前计算 session_len,再在主查询中复用:
WITH session_stats AS (
SELECT
channel,
session_id,
COUNT(*) OVER (PARTITION BY session_id) AS session_len
FROM logs
WHERE session_id IS NOT NULL
)
SELECT
channel,
COUNT(*) FILTER (WHERE session_len = 1) * 1.0 / COUNT(*) AS bounce_rate
FROM session_stats
GROUP BY channel;CTE 在这里不是语法糖,而是让优化器更容易复用窗口计算结果。如果表没建 session_id 索引,记得补上——否则 COUNT() OVER (PARTITION BY session_id) 会触发全表排序。
真正卡住的往往不是函数怎么写,而是 session 切分逻辑是否和业务埋点一致、NULL 值有没有静默污染统计口径。

















