在双十一当天,按每 2 小时为一个时间段(0-2, 2-4 ... 22-24),统计每个时间段的 GMV 占当天全天 GMV 的百分比
面试真题:双十一分时段 GMV 占比统计
1. 题目背景
在电商年度大促(如双十一)期间,运营团队需要实时或离线监控不同时段的销售爆发情况。本题要求统计双十一当天,每 2 小时为一个时间段(例如 00:00:00-01:59:59 计为 0-2,以此类推),每个时间段的 GMV(成交总额)占当天全天总 GMV 的比例。
2. 样例数据
输入表:user_orders(用户订单表)
| order_id | user_id | pay_time | order_amount |
|---|---|---|---|
| o101 | u01 | 2023-11-11 00:15:30 | 1500.00 |
| o102 | u02 | 2023-11-11 01:45:00 | 500.00 |
| o103 | u03 | 2023-11-11 03:10:20 | 1000.00 |
| o104 | u04 | 2023-11-11 10:30:00 | 2000.00 |
| o105 | u05 | 2023-11-11 23:15:00 | 5000.00 |
| o106 | u06 | 2023-11-12 01:00:00 | 3000.00 |
期望输出结果
| time_interval | interval_gmv | total_gmv | gmv_percentage |
|---|---|---|---|
| 0-2 | 2000.00 | 10000.00 | 20.00% |
| 2-4 | 1000.00 | 10000.00 | 10.00% |
| 10-12 | 2000.00 | 10000.00 | 20.00% |
| 22-24 | 5000.00 | 10000.00 | 50.00% |
SparkSQL 解决方案
sql
WITH t_base AS (
SELECT
order_amount,
-- 计算每笔订单属于哪个2小时区间
-- hour() 获取小时(0-23),floor(hour/2)*2 得到区间起点,再拼接终点
CONCAT(FLOOR(HOUR(pay_time) / 2) * 2, '-', FLOOR(HOUR(pay_time) / 2) * 2 + 2) AS time_interval
FROM user_orders
-- 严格限制在双十一当天
WHERE pay_time >= '2023-11-11 00:00:00' AND pay_time < '2023-11-12 00:00:00'
),
t_interval_gmv AS (
SELECT
time_interval,
SUM(order_amount) AS interval_gmv
FROM t_base
GROUP BY time_interval
)
SELECT
time_interval,
ROUND(interval_gmv, 2) AS interval_gmv,
ROUND(SUM(interval_gmv) OVER(), 2) AS total_gmv,
CONCAT(ROUND((interval_gmv / SUM(interval_gmv) OVER()) * 100, 2), '%') AS gmv_percentage
FROM t_interval_gmv
ORDER BY CAST(SPLIT(time_interval, '-')[0] AS INT);
SparkSQL 深度分析与面试加分项
在面试中,仅仅写出上述 SQL 是不够的。面试官往往会针对你的 SQL 方案进行深挖。以下是针对本题的深度解析和调优技巧,帮助你在面试中脱颖而出:
1. 时间分段技巧(Mathematical Bucketing)
- 常见误区:初学者往往会写一堆
CASE WHEN HOUR(pay_time) BETWEEN 0 AND 1 THEN '0-2' ...。这种写法极其冗长且不易维护。 - 优雅解法:利用数学公式
FLOOR(HOUR(pay_time) / N) * N进行分箱。- 若按 2 小时分段:
FLOOR(HOUR(pay_time) / 2) * 2。 - 若按 4 小时分段:
FLOOR(HOUR(pay_time) / 4) * 4。 - 该技巧展示了你具备良好的逻辑抽象能力。
- 若按 2 小时分段:
2. 窗口函数避免 Self-Join 性能瓶颈
- 传统做法:先
GROUP BY计算每个时段的 GMV,再用一条SELECT SUM(order_amount) FROM table的子查询进行JOIN来计算占比。 - 优化做法(如上所示):使用窗口函数
SUM(interval_gmv) OVER()。- 原理:
OVER()括号留空表示将整个结果集作为一个窗口(无分组无排序),Spark 会在内存中直接对聚合后的轻量级结果集求和。 - 性能优势:避免了将大表进行两次扫描和不必要的关联(Join)操作,极大减少了 Shuffle,提升了 Spark 执行计划的效率。
- 原理:
3. 执行计划与 Shuffle 调优(高级加分项)
当被问及“如果数据量极大(百亿级),这个任务在 Spark 中如何调优”时,可以从以下几个维度回答:
- 过滤下推(Predicate Pushdown):
在t_base中,我们使用了pay_time >= '2023-11-11 00:00:00'。如果user_orders表是按天分区的(例如分区字段为dt),应优先使用dt = '2023-11-11'过滤。这样 Spark 可以直接进行分区裁剪,避免全表扫描。 - 数据倾斜(Data Skew)处理:
- 问题:双十一当天,0点到2点(0-2)往往是下单高峰期,数据量远超其他时段,这会导致
GROUP BY time_interval时发生严重的数据倾斜(某个 Reducer 处理的数据量极大,导致 Stage 拖尾)。 - 解决方案(两阶段聚合):
如果出现倾斜,可以引入随机双重聚合。先给 key 加随机前缀进行局部聚合,再去掉前缀进行全局聚合:sql-- 1. 局部聚合 SELECT time_interval, SUM(order_amount) FROM (SELECT time_interval, order_amount, CONCAT(time_interval, '_', FLOOR(RAND() * 10)) as stage1_key FROM t_base) GROUP BY stage1_key, time_interval
- 问题:双十一当天,0点到2点(0-2)往往是下单高峰期,数据量远超其他时段,这会导致
- 窗口函数的执行位置:
在最后的SELECT中,SUM(interval_gmv) OVER()是在t_interval_gmv(仅有 12 行数据,因为 24 小时分为 12 个段)的基础上运行的。由于数据集极小,此时的窗口操作完全在单节点的单个 Partition 内瞬间完成,没有任何性能压力。