计算每个快递柜每天快递员投递数量最多的小时段,与用户取件数量最多的小时段,并计算这两个高峰时段之间的平均间隔时间
SparkSQL 面试题:快递柜高峰时段及间隔时间分析
1. 题目背景与要求
在智能快递柜运营中,了解快递员投递高峰期与用户取件高峰期对于优化快递柜周转率、调整投递策略至关重要。
请编写 SparkSQL 查询,计算每个快递柜每天:
- 快递员投递数量最多的小时段(如果投递量相同,取最早的小时)。
- 用户取件数量最多的小时段(如果取件量相同,取最早的小时)。
- 计算这两个高峰时段之间的平均间隔时间(单位:小时,取绝对值)。
2. 示例数据
表 1:快递柜操作流水表 (cabinet_records)
记录了快递员投递(put)和用户取件(get)的明细数据。
| record_id (记录ID) | cabinet_id (快递柜ID) | op_type (操作类型: put/get) | op_time (操作时间) |
|---|---|---|---|
| 1001 | CAB001 | put | 2023-10-25 08:15:00 |
| 1002 | CAB001 | put | 2023-10-25 08:30:00 |
| 1003 | CAB001 | put | 2023-10-25 09:10:00 |
| 1004 | CAB001 | get | 2023-10-25 18:20:00 |
| 1005 | CAB001 | get | 2023-10-25 18:45:00 |
| 1006 | CAB001 | get | 2023-10-25 19:15:00 |
| 1007 | CAB002 | put | 2023-10-25 10:05:00 |
| 1008 | CAB002 | put | 2023-10-25 10:50:00 |
| 1009 | CAB002 | get | 2023-10-25 12:10:00 |
| 1010 | CAB002 | get | 2023-10-25 12:30:00 |
3. 期望输出结果
| op_date (日期) | cabinet_id (快递柜ID) | peak_put_hour (投递高峰小时) | peak_get_hour (取件高峰小时) | hour_gap (高峰间隔小时数) |
|---|---|---|---|---|
| 2023-10-25 | CAB001 | 8 | 18 | 10.0 |
| 2023-10-25 | CAB002 | 10 | 12 | 2.0 |
4. SparkSQL 参考答案
sql
WITH base_hourly_stats AS (
-- Step 1: 统计每个快递柜每天每个小时的投递与取件数量
SELECT
TO_DATE(op_time) AS op_date,
cabinet_id,
HOUR(op_time) AS op_hour,
SUM(CASE WHEN op_type = 'put' THEN 1 ELSE 0 END) AS put_cnt,
SUM(CASE WHEN op_type = 'get' THEN 1 ELSE 0 END) AS get_cnt
FROM cabinet_records
GROUP BY TO_DATE(op_time), cabinet_id, HOUR(op_time)
),
ranked_stats AS (
-- Step 2: 结合窗口函数,分别对投递和取件数量进行降序排名
-- 若数量相同,按小时升序排列(取最早的小时)
SELECT
op_date,
cabinet_id,
op_hour,
ROW_NUMBER() OVER(
PARTITION BY op_date, cabinet_id
ORDER BY put_cnt DESC, op_hour ASC
) AS put_rank,
ROW_NUMBER() OVER(
PARTITION BY op_date, cabinet_id
ORDER BY get_cnt DESC, op_hour ASC
) AS get_rank
FROM base_hourly_stats
),
peak_put AS (
-- Step 3: 筛选出每日投递高峰小时
SELECT op_date, cabinet_id, op_hour AS peak_put_hour
FROM ranked_stats
WHERE put_rank = 1
),
peak_get AS (
-- Step 4: 筛选出每日取件高峰小时
SELECT op_date, cabinet_id, op_hour AS peak_get_hour
FROM ranked_stats
WHERE get_rank = 1
)
-- Step 5: 关联投递与取件高峰,并计算绝对差值
SELECT
p.op_date,
p.cabinet_id,
p.peak_put_hour,
g.peak_get_hour,
CAST(ABS(p.peak_put_hour - g.peak_get_hour) AS DOUBLE) AS hour_gap
FROM peak_put p
JOIN peak_get g
ON p.op_date = g.op_date AND p.cabinet_id = g.cabinet_id
ORDER BY p.op_date, p.cabinet_id;
5. SparkSQL 面试题深度分析与考察点
此题是典型的数据仓库/数据开发岗位的高频面试题,融合了时间序列分析、行转列、窗口函数和多表关联等综合能力。
考察点 1:时间维度处理 (Temporal Data Processing)
- 面试官意图:考察候选人对日期和时间函数的熟悉程度。
- 技术细节:代码中使用了
TO_DATE(op_time)提取日期作为分组键,使用HOUR(op_time)提取小时。这是时序分析的基础。
考察点 2:指标分类聚合 (Conditional Aggregation)
- 面试官意图:如何在一张流水表中高效区分并统计两个对立维度的指标。
- 技术细节:使用
SUM(CASE WHEN ... THEN 1 ELSE 0 END)的条件聚合方式,避免了多次读取原表(比使用WHERE op_type='put'和WHERE op_type='get'分别建临时表再JOIN性能高得多)。
考察点 3:复杂窗口函数与 Top-N 限制
- 面试官意图:考察候选人如何处理“并列第一”以及“Top 1”的业务策略。
- 技术细节:
- 使用
ROW_NUMBER()而不是RANK():因为题目要求“如果数量相同,取最早的小时”,ROW_NUMBER()结合ORDER BY count DESC, hour ASC可以保证每个柜子每天只精确输出一行最合适的高峰小时数据。 - 窗口函数中
PARTITION BY的粒度控制(按天+快递柜)。
- 使用
考察点 4:执行计划与性能优化 (Spark SQL Tuning)
在实际面试的追问环节,面试官可能会问:“如果 cabinet_records 表有几十亿条数据,这个 SQL 该如何优化?”
- 数据倾斜预防:
base_hourly_stats步骤中,按TO_DATE(op_time), cabinet_id, HOUR(op_time)进行GROUP BY。由于快递柜 ID 分布通常较均匀,极少出现单柜极度倾斜,但如果某些大城市快递柜极多,可以考虑开启Spark AQE(Adaptive Query Execution) 自动处理倾斜。 - Map 侧聚合:Spark 默认会启用
hash-based aggregation。由于我们在第一步就进行了GROUP BY聚合,已经极大地减少了 Shuffle 传输的数据量,后续的窗口函数计算压力会小很多。 - 避免多次 Scan:本答案在
ranked_stats中只使用了一次ROW_NUMBER对同一个 CTE 进行双窗口排序,随后通过拆分过滤,避免了对原始流水表的反复读取,IO 效率极高。