SQL 助力免单活动用户行为路径:分享→点击→助力→下单全链路(拼多多面试题)
一、题目背景
这道题来自拼多多社交裂变与增长部门的数据分析岗面试。"助力免单"是拼多多社交裂变体系中的王牌玩法——用户发起免单活动,将链接分享到微信群或朋友圈,邀请好友为自己助力。集齐规定数量的助力(通常3-10人)后,用户即可0元获得商品。这一机制是拼多多低成本病毒获客的核心引擎:每一次分享都是一次免费的社交传播,每一位点击助力的好友都可能被转化为新用户。
从数据分析视角,助力免单活动是一个典型的多环节漏斗:发起 → 分享 → 好友点击 → 完成助力 → 免单成功/失败。每个环节都可能流失——用户发起了但不分享(缺乏社交动力),分享了但好友不点击(文案吸引力不足),好友点了但不完成助力(流程太复杂),助力够了但超出时限。分析师需要监控每个活动在各环节的数据,定位瓶颈并评估不同活动模板的效果。
数据层面,这个场景关联三张表——发起表、分享表、助力表——核心挑战在于:left join 导致的笛卡尔积膨胀。一个活动发起1次、分享3次、有8个好友助力 → join 后产生 1×3×8 = 24 行数据,直接用 count(*) 计数会把发起次数和分享次数都算错,必须用 count(distinct) 逐字段去重。
业务场景:裂变运营团队每日监控"助力免单漏斗",重点关注发起→分享转化率(反映活动吸引力)和分享→助力转化率(反映社交传播效率)。考察的是多表 left join 防膨胀、
count(distinct case when)条件去重、以及业务规则(助力阈值硬编码 vs 配置化)的思考深度。
二、题目
拼多多"助力免单"活动中,用户发起免单活动后,需要邀请好友助力。分析每个活动的完整链路:发起→分享→好友助力→成功免单/失败,统计每个活动在各环节的人数及最终的免单成功率。
假设有三张表:
t10_activity_launch:用户发起免单活动表t10_activity_share:用户分享记录表t10_activity_help:好友助力记录表
-- t10_activity_launch 发起表
+-------------+---------+-------------------+
| activity_id | user_id | launch_time |
+-------------+---------+-------------------+
| act001 | u01 | 2025-06-01 10:00 |
| act002 | u02 | 2025-06-01 11:00 |
| act003 | u03 | 2025-06-02 09:00 |
| act004 | u04 | 2025-06-02 10:00 |
+-------------+---------+-------------------+
-- t10_activity_share 分享表
+-------------+-------------------+
| activity_id | share_time |
+-------------+-------------------+
| act001 | 2025-06-01 10:05 |
| act001 | 2025-06-01 10:10 |
| act002 | 2025-06-01 11:05 |
| act003 | 2025-06-02 09:10 |
+-------------+-------------------+
-- t10_activity_help 助力表
+-------------+-------------+-------------------+--------+
| activity_id | helper_id | help_time | status |
+-------------+-------------+-------------------+--------+
| act001 | h01 | 2025-06-01 10:30 | done |
| act001 | h02 | 2025-06-01 10:45 | done |
| act001 | h03 | 2025-06-01 11:00 | done |
| act001 | h04 | 2025-06-01 11:30 | done |
| act001 | h05 | 2025-06-01 12:00 | done |
| act002 | h06 | 2025-06-01 11:30 | done |
| act002 | h07 | 2025-06-01 12:00 | done |
| act003 | h08 | 2025-06-02 09:30 | done |
+-------------+-------------+-------------------+--------+
假设免单规则:需要集齐5个助力才算免单成功。
三、思路分析
- 以发起表为主表,
left join分享表和助力表; - 按活动聚合:分享次数(去重分享行为)、助力人数、助力完成人数;
- 使用
case when条件聚合判断免单成功条件:助力完成人数 >= 5; - 难度在于多表
left join导致的笛卡尔积膨胀,必须用count(distinct)去重计数。
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐️⭐️⭐️⭐️ |
| 题目清晰度 | ⭐️⭐️⭐️ |
| 业务常见度 | ⭐️⭐️⭐️⭐️⭐️ |
四、逐步推导
1.按活动聚合各环节指标
执行SQL
select l.activity_id,
l.user_id,
count(distinct s.share_time) as share_cnt,
count(distinct h.helper_id) as help_user_cnt,
count(distinct case when h.status = 'done' then h.helper_id end) as help_done_cnt
from t10_activity_launch l
left join t10_activity_share s
on l.activity_id = s.activity_id
left join t10_activity_help h
on l.activity_id = h.activity_id
group by l.activity_id, l.user_id
查询结果
+-------------+---------+-----------+---------------+---------------+
| activity_id | user_id | share_cnt | help_user_cnt | help_done_cnt |
+-------------+---------+-----------+---------------+---------------+
| act001 | u01 | 2 | 5 | 5 |
| act002 | u02 | 1 | 2 | 2 |
| act003 | u03 | 1 | 1 | 1 |
| act004 | u04 | 0 | 0 | 0 |
+-------------+---------+-----------+---------------+---------------+
2.判断免单成功,计算成功率
执行SQL
select activity_id,
user_id,
share_cnt,
help_user_cnt,
help_done_cnt,
case when help_done_cnt >= 5 then '免单成功' else '免单失败' end as result
from (
select l.activity_id,
l.user_id,
count(distinct s.share_time) as share_cnt,
count(distinct h.helper_id) as help_user_cnt,
count(distinct case when h.status = 'done' then h.helper_id end) as help_done_cnt
from t10_activity_launch l
left join t10_activity_share s
on l.activity_id = s.activity_id
left join t10_activity_help h
on l.activity_id = h.activity_id
group by l.activity_id, l.user_id
) t
查询结果
+-------------+---------+-----------+---------------+---------------+----------+
| activity_id | user_id | share_cnt | help_user_cnt | help_done_cnt | result |
+-------------+---------+-----------+---------------+---------------+----------+
| act001 | u01 | 2 | 5 | 5 | 免单成功 |
| act002 | u02 | 1 | 2 | 2 | 免单失败 |
| act003 | u03 | 1 | 1 | 1 | 免单失败 |
| act004 | u04 | 0 | 0 | 0 | 免单失败 |
+-------------+---------+-----------+---------------+---------------+----------+
五、常见坑点
坑1:left join 多表关联导致行数膨胀(扇出) — 1条发起记录 × N条分享记录 × M条助力记录,若不使用 count(distinct) 而去直接 count(*),分享次数和助力人数会被严重放大。必须用 count(distinct share_time) 和 count(distinct helper_id) 来去重计数。
坑2:count(distinct share_time) 作为分享次数的代理指标不够精确 — 如果同一个 share_time 恰好出现了两次(如数据重复录入),会被去重算作一次。更严格的做法是在分享表中增加 share_id 主键,用 count(distinct share_id) 来计数。
坑3:免单阈值硬编码在 SQL 中 — case when help_done_cnt >= 5 中的 5 是硬编码的。实际业务中,不同活动的免单门槛可能不同(如新用户活动只需3人助力)。合理做法是将阈值配置在活动规则表中,通过 join 动态获取,避免业务规则变更时批量修改 SQL。
六、举一反三
-
计算各环节转化率:在现有结果上,计算发起→分享转化率(
share_cnt > 0 的活动数 / 总活动数)和分享→助力转化率(help_user_cnt > 0 的活动数 / share_cnt > 0 的活动数),定位漏斗中最薄弱的环节。 -
分析助力响应速度:对每次分享,计算从分享时间到第一条助力时间的间隔(
min(help_time) - share_time),按活动聚合成平均首助时长。首助速度越快,说明分享触达效率越高,可作为社交关系强度的代理指标。 -
计算病毒系数:统计每个分享者平均带来的助力者人数(
help_user_cnt / share_cnt),以及助力者中有多少转化为了新发起者。病毒系数 > 1 说明活动具有自传播效应,是衡量社交裂变效率的核心指标。
七、知识点总结
| 考点 | 说明 |
|---|---|
| count(distinct) 去重聚合 | 多表 left join 产生扇出后,必须用 count(distinct) 才能得到正确的计数 |
| case when 条件聚合 | 在聚合函数内嵌入 case when,灵活实现条件计数和标签化处理 |
| 多表 left join | 以主表为基准保留所有活动,即使没有分享或助力记录也不丢失 |
| group by + 聚合函数 | 按活动维度分组,统计每个活动的各环节指标 |
八、建表语句和数据插入
点击展开 DDL & DML
-- 建表语句
create table t10_activity_launch (
activity_id string comment '活动id',
user_id string comment '发起用户id',
launch_time string comment '发起时间'
) comment '免单活动发起表';
create table t10_activity_share (
activity_id string comment '活动id',
share_time string comment '分享时间'
) comment '免单活动分享表';
create table t10_activity_help (
activity_id string comment '活动id',
helper_id string comment '助力者id',
help_time string comment '助力时间',
status string comment '助力状态:done-完成,cancel-取消'
) comment '免单活动助力表';
-- 插入数据
insert into t10_activity_launch(activity_id, user_id, launch_time) values
('act001', 'u01', '2025-06-01 10:00'),
('act002', 'u02', '2025-06-01 11:00'),
('act003', 'u03', '2025-06-02 09:00'),
('act004', 'u04', '2025-06-02 10:00');
insert into t10_activity_share(activity_id, share_time) values
('act001', '2025-06-01 10:05'),
('act001', '2025-06-01 10:10'),
('act002', '2025-06-01 11:05'),
('act003', '2025-06-02 09:10');
insert into t10_activity_help(activity_id, helper_id, help_time, status) values
('act001', 'h01', '2025-06-01 10:30', 'done'),
('act001', 'h02', '2025-06-01 10:45', 'done'),
('act001', 'h03', '2025-06-01 11:00', 'done'),
('act001', 'h04', '2025-06-01 11:30', 'done'),
('act001', 'h05', '2025-06-01 12:00', 'done'),
('act002', 'h06', '2025-06-01 11:30', 'done'),
('act002', 'h07', '2025-06-01 12:00', 'done'),
('act003', 'h08', '2025-06-02 09:30', 'done');
「数据仓库技术」文章同步更新,不错过每一篇干货

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