快手大数据面试SQL-视频播放进度分析(完播率统计)
⚠️ 待修正
一、题目背景
这道题来自快手数据平台部的数据分析岗面试。快手作为短视频平台,完播率是衡量视频内容质量的核心指标——用户是否完整观看了视频,直接决定了算法推荐权重。完播率高的视频会获得更多流量倾斜,因此精准统计完播率对内容分发策略至关重要。
业务场景:推荐算法团队需要根据完播率筛选优质视频进入更大的流量池。同时运营团队也需要按完播率评估不同类型内容的吸引力,指导创作者的内容方向调整。每个用户对同一视频的重复播放只取最长播放记录,避免因用户反复观看同一视频导致数据失真。
二、题目
已知有表t1_video_play_log记录了用户视频播放行为,包含字段:video_id(视频ID)、user_id(用户ID)、video_duration(视频总时长,单位秒)、play_duration(用户实际播放时长,单位秒)、play_date(播放日期)。
请统计每个视频的完播率以及整体完播率。完播的定义为:用户实际播放时长 >= 视频总时长 * 0.9(即播放超过90%视为完播)。每个用户对同一个视频只计算一次(取该用户最长播放记录)。
样例数据
+----------+---------+----------------+---------------+------------+
| video_id | user_id | video_duration | play_duration | play_date |
+----------+---------+----------------+---------------+------------+
| V001 | u001 | 60 | 60 | 2024-06-01 |
| V001 | u002 | 60 | 45 | 2024-06-01 |
| V001 | u003 | 60 | 55 | 2024-06-01 |
| V001 | u003 | 60 | 58 | 2024-06-02 |
| V001 | u004 | 60 | 10 | 2024-06-01 |
| V002 | u001 | 120 | 120 | 2024-06-01 |
| V002 | u002 | 120 | 30 | 2024-06-01 |
| V002 | u005 | 120 | 115 | 2024-06-01 |
| V003 | u001 | 30 | 28 | 2024-06-01 |
| V003 | u004 | 30 | 5 | 2024-06-01 |
+----------+---------+----------------+---------------+------------+
三、思路分析
本题是短视频平台中非常核心的播放质量指标——完播率。完播率高的视频通常会被算法优先推荐。
核心步骤:
- 用户去重:每个用户对同一个视频可能有多次播放记录,需要按(video_id, user_id)分组,取该用户对该视频的最长播放时长。使用ROW_NUMBER可以完成这个操作。
- 完播判断:对去重后的数据,判断play_duration >= video_duration * 0.9。这里用CASE WHEN或直接布尔判断。
- 完播率计算:按video_id分组,完播记录数 / 总记录数。整体完播率可以通过去掉GROUP BY直接计算。
在实际业务中,完播率还可以按视频时长区间(如 <15s、15-60s、>60s)、按视频类型等维度进一步细分。
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐️⭐️ |
| 题目清晰度 | ⭐️⭐️⭐️⭐️⭐️ |
| 业务常见度 | ⭐️⭐️⭐️⭐️⭐️ |
四、逐步推导
步骤1:用户去重——每个用户对每个视频取最长播放记录
使用ROW_NUMBER按(video_id, user_id)分组,按play_duration降序排序,取rn=1即为每个用户对每个视频的最长播放记录。
执行SQL
select
video_id,
user_id,
video_duration,
play_duration,
play_date
from (
select
video_id,
user_id,
video_duration,
play_duration,
play_date,
row_number() over (partition by video_id, user_id order by play_duration desc) as rn
from t1_video_play_log
) t
where rn = 1
执行结果
+----------+---------+----------------+---------------+------------+
| video_id | user_id | video_duration | play_duration | play_date |
+----------+---------+----------------+---------------+------------+
| V001 | u001 | 60 | 60 | 2024-06-01 |
| V001 | u002 | 60 | 45 | 2024-06-01 |
| V001 | u003 | 60 | 58 | 2024-06-02 |
| V001 | u004 | 60 | 10 | 2024-06-01 |
| V002 | u001 | 120 | 120 | 2024-06-01 |
| V002 | u002 | 120 | 30 | 2024-06-01 |
| V002 | u005 | 120 | 115 | 2024-06-01 |
| V003 | u001 | 30 | 28 | 2024-06-01 |
| V003 | u004 | 30 | 5 | 2024-06-01 |
+----------+---------+----------------+---------------+------------+
可以看到u003对V001有两条记录(55s和58s),去重后仅保留了最长播放的58s记录。
步骤2:统计每个视频的完播率
按video_id分组,使用条件聚合计算完播用户数和总用户数,完播率 = 完播用户数 / 总用户数。
执行SQL
with user_video as (
select
video_id,
user_id,
video_duration,
play_duration
from (
select
video_id,
user_id,
video_duration,
play_duration,
row_number() over (partition by video_id, user_id order by play_duration desc) as rn
from t1_video_play_log
) t
where rn = 1
)
select
video_id,
count(distinct user_id) as total_users,
sum(case when play_duration >= video_duration * 0.9 then 1 else 0 end) as completed_users,
round(
sum(case when play_duration >= video_duration * 0.9 then 1 else 0 end) * 100.0
/ count(distinct user_id),
2
) as completion_rate
from user_video
group by video_id
执行结果
+----------+-------------+-----------------+-----------------+
| video_id | total_users | completed_users | completion_rate |
+----------+-------------+-----------------+-----------------+
| V001 | 4 | 1 | 25.00 |
| V002 | 3 | 2 | 66.67 |
| V003 | 2 | 1 | 50.00 |
+----------+-------------+-----------------+-----------------+
步骤3:计算平台整体完播率
不按video_id分组,直接计算所有视频的整体完播率。
执行SQL
with user_video as (
select
video_id,
user_id,
video_duration,
play_duration
from (
select
video_id,
user_id,
video_duration,
play_duration,
row_number() over (partition by video_id, user_id order by play_duration desc) as rn
from t1_video_play_log
) t
where rn = 1
)
select
count(distinct user_id) as total_playbacks,
sum(case when play_duration >= video_duration * 0.9 then 1 else 0 end) as completed_playbacks,
round(
sum(case when play_duration >= video_duration * 0.9 then 1 else 0 end) * 100.0
/ count(distinct user_id),
2
) as overall_completion_rate
from user_video
执行结果
+-----------------+----------------------+---------------------------+
| total_playbacks | completed_playbacks | overall_completion_rate |
+-----------------+----------------------+---------------------------+
| 9 | 4 | 44.44 |
+-----------------+----------------------+---------------------------+
五、常见坑点
坑1:忘记按用户去重,导致重复播放放大数据
同一个用户可能多次播放同一视频(如反复观看精彩片段)。如果不先去重,该用户的多次播放会被多次计入分母和分子,导致完播率失真。正确做法是先用ROW_NUMBER取每个用户对每个视频的最长播放记录,再去计算指标。
坑2:完播阈值设错导致结果偏差
完播的阈值通常不是100%而是90%或95%。因为用户可能在最后1-2秒滑动到下一个视频,如果严格用100%判断,完播率会极低,失去参考价值。本题明确要求90%,但如果面试中没有说明,需要主动向面试官确认阈值口径。
坑3:COUNT(DISTINCT user_id) 在整体完播率中的歧义
计算整体完播率时,一个用户可能播放了多个视频。如果直接用 COUNT(DISTINCT user_id) 会低估总数的实际含义——分母应该是"去重后的(视频,用户)对"的数量,而不是"去重用户数"。示例数据中因为没有跨视频的重复用户所以碰巧一致,但实际业务中需要注意这个区别。
六、举一反三
-
按时长区间分档统计完播率:将视频按总时长分为 <15s(短视频)、15-60s(中视频)、>60s(长视频)三档,在GROUP BY中加入时长区间维度,分析不同时长视频的完播表现差异,指导内容创作方向。
-
完播率+互动率复合指标:在完播的基础上,还可关联评论/点赞/分享表,计算"完播且互动"的比率,这是比单纯完播率更强的质量信号,常用于精选推荐池的准入标准。
-
计算用户的完播偏好:按user_id分组统计每个用户的平均完播率,用于用户画像标签建设。完播率高的用户对内容更挑剔,可以给他们推更优质的内容;完播率低的用户可能是"滑动党",适合推短平快的内容。
-
完播率的每日趋势监控:按play_date分组统计每天的完播率变化趋势,可配合A/B测试或算法改动做效果评估——完播率的涨跌直接反映推荐策略的优劣。
七、知识点总结
| 考点 | 说明 |
|---|---|
| ROW_NUMBER() 去重 | 按(video_id, user_id)分区取最长播放记录 |
| CASE WHEN 条件聚合 | 判断 play_duration >= video_duration * 0.9 为完播 |
| WITH CTE 复用子查询 | 将去重结果封装为CTE,避免重复编写子查询 |
| 比率计算 + ROUND | 用条件聚合结果除以COUNT得到百分比,注意乘以100.0避免整数除法 |
八、建表语句和数据插入
点击展开 DDL & DML
--建表语句
CREATE TABLE IF NOT EXISTS t1_video_play_log (
video_id STRING COMMENT '视频ID',
user_id STRING COMMENT '用户ID',
video_duration BIGINT COMMENT '视频总时长(秒)',
play_duration BIGINT COMMENT '用户实际播放时长(秒)',
play_date STRING COMMENT '播放日期'
);
--数据插入
INSERT INTO t1_video_play_log(video_id, user_id, video_duration, play_duration, play_date) VALUES
('V001', 'u001', 60, 60, '2024-06-01'),
('V001', 'u002', 60, 45, '2024-06-01'),
('V001', 'u003', 60, 55, '2024-06-01'),
('V001', 'u003', 60, 58, '2024-06-02'),
('V001', 'u004', 60, 10, '2024-06-01'),
('V002', 'u001', 120, 120, '2024-06-01'),
('V002', 'u002', 120, 30, '2024-06-01'),
('V002', 'u005', 120, 115, '2024-06-01'),
('V003', 'u001', 30, 28, '2024-06-01'),
('V003', 'u004', 30, 5, '2024-06-01');
「数据仓库技术」文章同步更新,不错过每一篇干货

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