跳到主要内容

SQL 视频互动率:用户行为日志聚合+比率计算(B站面试题)

一、题目背景

这道题来自B站内容运营部门的数据分析岗面试。B站的视频互动数据(投币、点赞、收藏)是衡量内容质量的核心指标——"一键三连"更是B站的站内文化符号。在实际数据仓库中,用户行为数据以明细日志形式存储在ODS层,每一条播放、点赞、投币、收藏都是一行独立的记录。

业务场景:分析师需要从原始行为日志表中,先按视频+行为类型聚合计数,再计算各项互动率。这是从ODS到DWS层聚合的典型ETL场景,考察的是"先聚合、再计算"的分层思维。

二、题目

现有一张用户行为日志表 t2_user_action,记录用户对视频的每次操作。请统计每个视频的播放量、点赞数、投币数、收藏数,并计算投币率、点赞率、收藏率。

比率公式
投币率投币数 / 播放量
点赞率点赞数 / 播放量
收藏率收藏数 / 播放量

表 t2_user_action:

+----------+-----------+--------------+----------------------+
| user_id | video_id | action_type | action_time |
+----------+-----------+--------------+----------------------+
| u01 | BV001 | play | 2023-03-01 10:00:00 |
| u01 | BV001 | like | 2023-03-01 10:05:00 |
| u01 | BV001 | coin | 2023-03-01 10:05:30 |
| u02 | BV001 | play | 2023-03-01 10:10:00 |
| u02 | BV001 | favorite | 2023-03-01 10:10:30 |
| u03 | BV001 | play | 2023-03-01 10:15:00 |
| u01 | BV002 | play | 2023-03-01 11:00:00 |
| u01 | BV002 | like | 2023-03-01 11:02:00 |
| u02 | BV002 | play | 2023-03-01 11:05:00 |
| u02 | BV002 | coin | 2023-03-01 11:06:00 |
| u02 | BV002 | favorite | 2023-03-01 11:06:30 |
+----------+-----------+--------------+----------------------+

三、思路分析

核心思路是 "先聚合、再计算" —— 这是ODS明细层→DWS汇总层的标准模式:

  1. 行转列聚合:用 case when + countaction_type 字段的不同取值展开为独立的计数列(播放量/点赞数/投币数/收藏数),按 video_id 分组
  2. 计算比率:在聚合结果上用除法计算各项互动率,用 nullif 防止除零报错,用 round 控制精度
维度评分
题目难度⭐️⭐️
题目清晰度⭐️⭐️⭐️⭐️⭐️
业务常见度⭐️⭐️⭐️⭐️⭐️

四、逐步推导

步骤1:从行为日志中按视频聚合各行为类型的计数

select video_id,
count(case when action_type = 'play' then 1 end) as play_cnt,
count(case when action_type = 'like' then 1 end) as like_cnt,
count(case when action_type = 'coin' then 1 end) as coin_cnt,
count(case when action_type = 'favorite' then 1 end) as fav_cnt
from t2_user_action
group by video_id

执行结果

+-----------+-----------+-----------+-----------+----------+
| video_id | play_cnt | like_cnt | coin_cnt | fav_cnt |
+-----------+-----------+-----------+-----------+----------+
| BV001 | 3 | 1 | 1 | 1 |
| BV002 | 2 | 1 | 1 | 1 |
+-----------+-----------+-----------+-----------+----------+
2 rows selected (0.991 seconds)(https://www.dwsql.com)

每条 action_type 只有一行 then 1count 统计。case when 未命中时返回 null,count 自动跳过 null,等价于统计命中次数。

步骤2:在聚合结果上计算三项互动率

select video_id, play_cnt, like_cnt, coin_cnt, fav_cnt,
round(coin_cnt / nullif(play_cnt, 0), 4) as coin_rate,
round(like_cnt / nullif(play_cnt, 0), 4) as like_rate,
round(fav_cnt / nullif(play_cnt, 0), 4) as fav_rate
from (
select video_id,
count(case when action_type = 'play' then 1 end) as play_cnt,
count(case when action_type = 'like' then 1 end) as like_cnt,
count(case when action_type = 'coin' then 1 end) as coin_cnt,
count(case when action_type = 'favorite' then 1 end) as fav_cnt
from t2_user_action
group by video_id
) t

执行结果

+-----------+-----------+-----------+-----------+----------+------------+------------+-----------+
| video_id | play_cnt | like_cnt | coin_cnt | fav_cnt | coin_rate | like_rate | fav_rate |
+-----------+-----------+-----------+-----------+----------+------------+------------+-----------+
| BV001 | 3 | 1 | 1 | 1 | 0.3333 | 0.3333 | 0.3333 |
| BV002 | 2 | 1 | 1 | 1 | 0.5 | 0.5 | 0.5 |
+-----------+-----------+-----------+-----------+----------+------------+------------+-----------+
2 rows selected (0.429 seconds)(https://www.dwsql.com)

五、常见坑点

坑1:sum vs count 在条件聚合中的差异

count(case when ... then 1 end) 统计命中行数;sum(case when ... then 1 else 0 end) 也是统计命中行数,但写法不同。注意 count 跳过 null 而 sum 不跳过 0,两者在大数据量下性能有差异:count 不处理 0 值行,通常更快。但如果需要统计的字段本身可能为 0(如某行为的计数列),两者结果相同。

坑2:同一个用户对同一视频重复操作

一个用户可能多次播放同一个视频,表中会有多条 action_type = 'play' 的记录。当前 SQL 会将重复播放计入 play_cnt,这符合"播放量"的业务口径。但如果需求是"播放人数"(UV),则需要用 count(distinct user_id) 替代。面试中应主动确认业务口径。

坑3:case when 分支遗漏导致 NULL 列

如果某视频只有播放和点赞、没有投币和收藏,coin_cntfav_cntcase when 全部不命中,count 返回 0(不是 NULL,因为 count 对空集返回 0)。但 nullif(play_cnt, 0)play_cnt=0 时返回 null,此时除法结果也是 null 而非报错——这正是防御性编程的正确行为。

六、举一反三

  1. 按分区维度聚合互动率:在 t2_user_action 基础上 join 视频信息表获取 category 字段,外层 group by 加上 category,对比游戏区 vs 知识区 vs 音乐区的平均互动率差异,辅助分区运营策略调整
  2. 分时段互动率对比:用 substr(action_time, 1, 10) 截取日期,按天统计互动率趋势,观察某视频发布后互动率的衰减曲线——发布时间24h内的互动率 vs 一周后的互动率
  3. 用户维度的互动行为画像:将 group by video_id 改为 group by user_id,统计每个用户的总播放量、总点赞数、总投币数,计算用户的"平均投币率",识别高互动价值用户(投币率 > 5%)给予社区勋章

七、知识点总结

考点说明
case when + count 条件聚合将行级行为类型展开为列级指标,日志明细→汇总指标的核心技巧
group by + 聚合按视频维度汇总用户行为,ODS→DWS的标准ETL模式
nullif(value, 0)将0转为null避免除零报错,除数null则结果自动为null
round(value, n)保留n位小数控制输出精度
子查询分层计算先聚合、再计算,拆分为子查询避免单层SQL过于复杂

八、建表语句和数据插入

点击展开 DDL & DML
create table t2_user_action (
user_id string comment '用户ID',
video_id string comment '视频ID(BV号)',
action_type string comment '行为类型:play=播放, like=点赞, coin=投币, favorite=收藏',
action_time string comment '行为发生时间'
) comment '用户视频行为日志表';

insert into t2_user_action values
('u01','BV001','play','2023-03-01 10:00:00'),
('u01','BV001','like','2023-03-01 10:05:00'),
('u01','BV001','coin','2023-03-01 10:05:30'),
('u02','BV001','play','2023-03-01 10:10:00'),
('u02','BV001','favorite','2023-03-01 10:10:30'),
('u03','BV001','play','2023-03-01 10:15:00'),
('u01','BV002','play','2023-03-01 11:00:00'),
('u01','BV002','like','2023-03-01 11:02:00'),
('u02','BV002','play','2023-03-01 11:05:00'),
('u02','BV002','coin','2023-03-01 11:06:00'),
('u02','BV002','favorite','2023-03-01 11:06:30');
📱关注公众号

「数据仓库技术」文章同步更新,不错过每一篇干货

微信公众号二维码
💬加群交流

备注「数据仓库技术」加入社群,每日一道大厂SQL真题

交流微信二维码

你可能还想看