基于本文回答

播面 播面

文解图解播客视频,多维讲透八股文
0
评论

计算每个快递柜每天快递员投递数量最多的小时段,与用户取件数量最多的小时段,并计算这两个高峰时段之间的平均间隔时间

SparkSQL 面试题:快递柜高峰时段及间隔时间分析

1. 题目背景与要求

在智能快递柜运营中,了解快递员投递高峰期用户取件高峰期对于优化快递柜周转率、调整投递策略至关重要。

请编写 SparkSQL 查询,计算每个快递柜每天

  1. 快递员投递数量最多的小时段(如果投递量相同,取最早的小时)。
  2. 用户取件数量最多的小时段(如果取件量相同,取最早的小时)。
  3. 计算这两个高峰时段之间的平均间隔时间(单位:小时,取绝对值)。

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 效率极高。