计算用户点击促销 Banner 广告到最终购买该促销商品的平均耗时(天数或小时数)
面试题:计算用户点击促销 Banner 广告到最终购买该促销商品的平均耗时
1. 业务背景与口径定义
在电商转化分析中,衡量用户从“产生兴趣(点击广告)”到“决策购买(下单)”的流转效率是一个核心指标。
为了保证计算的准确性,我们需要定义以下业务口径:
- 首次归因原则:计算用户首次点击某商品 Banner 广告,到其首次购买该商品之间的耗时。
- 时间先后顺序:购买行为必须发生在点击行为之后(
purchase_time > click_time)。 - 指标单位:结果输出为平均耗时(小时数,保留 2 位小数)。
2. 示例数据
表 1:广告点击表 (ad_clicks)
| user_id | product_id | click_time |
|---|---|---|
| 101 | 501 | 2023-10-01 10:00:00 |
| 101 | 501 | 2023-10-01 12:00:00 |
| 102 | 502 | 2023-10-01 14:00:00 |
| 103 | 501 | 2023-10-02 09:00:00 |
| 104 | 503 | 2023-10-03 15:00:00 |
表 2:订单表 (orders)
| user_id | product_id | purchase_time | amount |
|---|---|---|---|
| 101 | 501 | 2023-10-01 15:00:00 | 99.00 |
| 102 | 502 | 2023-10-02 14:00:00 | 199.00 |
| 103 | 501 | 2023-10-02 08:00:00 | 50.00 |
| 103 | 501 | 2023-10-02 11:00:00 | 50.00 |
| 105 | 501 | 2023-10-01 11:00:00 | 50.00 |
3. 面试考察点
- 逻辑严密性:能否排除“先买后点”(如用户 103 在 08:00 购买,09:00 才点击)的无效干扰数据。
- 多对多关联处理:同一用户可能多次点击、多次购买,如何准确锁定“点击后的首次购买”。
- 标准 SQL 函数应用:如何利用
WITH表达式(CTE)让代码结构清晰,并使用时间差函数(如TIMESTAMPDIFF)进行计算。
4. SQL 解决方案 (基于 MySQL 8.0+)
sql
WITH FirstClicks AS (
-- 步骤 1:获取每个用户对每个商品的最早点击时间(排除重复点击干扰)
SELECT
user_id,
product_id,
MIN(click_time) AS first_click_time
FROM ad_clicks
GROUP BY user_id, product_id
),
PostClickPurchases AS (
-- 步骤 2:关联订单表,筛选出发生在“最早点击”之后的购买记录,并取其最早的一次购买时间
SELECT
fc.user_id,
fc.product_id,
fc.first_click_time,
MIN(o.purchase_time) AS first_purchase_time
FROM FirstClicks fc
JOIN orders o
ON fc.user_id = o.user_id
AND fc.product_id = o.product_id
WHERE o.purchase_time > fc.first_click_time -- 核心条件:购买必须在点击之后
GROUP BY fc.user_id, fc.product_id, fc.first_click_time
)
-- 步骤 3:计算所有转化用户的平均耗时(单位:小时)
SELECT
ROUND(AVG(TIMESTAMPDIFF(SECOND, first_click_time, first_purchase_time) / 3600.0), 2) AS avg_hours_to_buy
FROM PostClickPurchases;
5. SQL 代码深度解析与数据流转
为了向面试官展现扎实的功底,可以主动拆解测试数据的流转过程:
FirstClicks临时表:- 过滤掉了用户 101 在
12:00:00的二次点击,只保留10:00:00的首次点击。
- 过滤掉了用户 101 在
PostClickPurchases临时表(关键过滤步骤):- 用户 101 (商品 501):最早点击
10:00,后续购买15:00,符合条件。耗时 小时。 - 用户 102 (商品 502):最早点击
14:00,后续购买次日14:00,符合条件。耗时 24 小时。 - 用户 103 (商品 501):虽然有
08:00的购买记录,但因为早于点击时间09:00被WHERE子句过滤;系统准确锁定了点击后的首次购买11:00。耗时 小时。 - 用户 104、用户 105:因无法匹配“既点击又购买”的链路,自动不参与计算。
- 用户 101 (商品 501):最早点击
- 最终聚合:
- 参与计算的耗时分别为:5小时、24小时、2小时。
- 平均耗时: 小时。
6. 面试官可能追问的扩展问题
追问 1:如果数据量极大(百亿级),上述 JOIN 性能很差,该如何优化?
- 解答思路:
- 限制时间窗口:通常点击到购买的转化不会跨越太久(如超过30天就失去广告归因意义)。可以在
JOIN时加上分区键限制或时间范围限制,例如WHERE o.purchase_time BETWEEN fc.first_click_time AND DATE_ADD(fc.first_click_time, INTERVAL 7 DAY),利用分区裁剪和索引减少扫描数据量。 - 避免大表 JOIN,使用窗口函数:在离线计算(Hive/Spark SQL)中,可以将两张表通过
UNION ALL合并成一张全量行为轨迹表,然后利用ROW_NUMBER()或LEAD()/LAG()窗口函数在单表内完成链路计算,避免高昂的Shuffle Join成本。
- 限制时间窗口:通常点击到购买的转化不会跨越太久(如超过30天就失去广告归因意义)。可以在
追问 2:如果我们要看不同“广告位置 (Banner位置)”的转化耗时,SQL 该怎么改?
- 解答思路:
在ad_clicks表中必然有banner_id或position字段。我们只需在FirstClicks阶段将banner_id纳入SELECT和GROUP BY中,并在最终的输出中按banner_id进行GROUP BY,即可对比不同位置广告的催化效率。