分析每个快递员投递快件的习惯,统计其投递的包裹在大、中、小三种规格格口中的占比分布
SparkSQL 面试题:快递员投递包裹规格占比分析
1. 题目背景与要求
在物流行业中,分析快递员的投递习惯对于优化快递柜的格口配置(大、中、小格口的比例)至关重要。
请编写一段 SparkSQL 逻辑,计算每个快递员投递的包裹在大、中、小三种规格格口中的占比分布(结果保留4位小数,并以百分比形式或小数形式呈现)。
2. 示例数据
表1:包裹投递明细表 (t_delivery_record)
记录了每次投递的流水信息。
| 投递ID (delivery_id) | 快递员ID (courier_id) | 包裹编号 (package_id) | 投递格口规格 (box_size) | 投递时间 (delivery_time) |
|---|---|---|---|---|
| d001 | C001 | P1001 | 大 (Large) | 2023-10-01 08:00:00 |
| d002 | C001 | P1002 | 中 (Medium) | 2023-10-01 08:30:00 |
| d003 | C001 | P1003 | 中 (Medium) | 2023-10-01 09:00:00 |
| d004 | C001 | P1004 | 小 (Small) | 2023-10-01 09:15:00 |
| d005 | C002 | P1005 | 大 (Large) | 2023-10-01 10:00:00 |
| d006 | C002 | P1006 | 大 (Large) | 2023-10-01 10:30:00 |
| d007 | C002 | P1007 | 小 (Small) | 2023-10-01 11:00:00 |
| d008 | C003 | P1008 | 中 (Medium) | 2023-10-01 11:15:00 |
期望输出结果
| 快递员ID (courier_id) | 总投递量 (total_count) | 大格口占比 (large_ratio) | 中格口占比 (medium_ratio) | 小格口占比 (small_ratio) |
|---|---|---|---|---|
| C001 | 4 | 0.2500 (25.00%) | 0.5000 (50.00%) | 0.2500 (25.00%) |
| C002 | 3 | 0.6667 (66.67%) | 0.0000 (0.00%) | 0.3333 (33.33%) |
| C003 | 1 | 0.0000 (0.00%) | 1.0000 (100.00%) | 0.0000 (0.00%) |
3. SparkSQL 核心解法
sql
SELECT
courier_id,
COUNT(1) AS total_count,
-- 使用 CASE WHEN 配合 SUM 进行行转列与条件计数
ROUND(SUM(CASE WHEN box_size = '大 (Large)' THEN 1 ELSE 0 END) / COUNT(1), 4) AS large_ratio,
ROUND(SUM(CASE WHEN box_size = '中 (Medium)' THEN 1 ELSE 0 END) / COUNT(1), 4) AS medium_ratio,
ROUND(SUM(CASE WHEN box_size = '小 (Small)' THEN 1 ELSE 0 END) / COUNT(1), 4) AS small_ratio
FROM
t_delivery_record
GROUP BY
courier_id;
4. SparkSQL 深度剖析与面试加分项
① 核心技术点:行转列(Pivot)的经典实现
本题是典型的“行转列”应用场景。在分布式计算中,频繁使用 JOIN 会导致多次 Shuffle,严重影响性能。
- 推荐做法:使用
GROUP BY + CASE WHEN + SUM。这种方式只需要对数据进行一次全表扫描和一次 Shuffle(Group By),即可同时计算出总数以及各个维度的子项数。 - Spark 专属优化:在 SparkSQL 中,也可以使用原生
PIVOT语法,但手写CASE WHEN具有更好的跨引擎兼容性(Hive/Presto/FlinkSQL 通用)。
② 避免“除以零(Division by Zero)”的健壮性设计
在实际生产环境中,如果 total_count 为 0(虽然在此业务场景下由于 GROUP BY 保证了至少为1,但如果是关联外部表则可能为0),直接相除会导致 NaN 或报错。
- 面试官加分点:展示你对数据脏值的敏感度。可以使用
NULLIF(COUNT(1), 0)来规避除零异常:sqlROUND(SUM(CASE WHEN box_size = '大 (Large)' THEN 1 ELSE 0 END) / NULLIF(COUNT(1), 0), 4)
③ Spark 执行计划与性能调优分析
- 数据倾斜(Data Skew):如果某些“头部快递员”每天投递几万件,而普通快递员只投递几十件,
GROUP BY courier_id会导致严重的数据倾斜。- 解决方案:
- 双重聚合(两阶段聚合):先对
courier_id加随机前缀进行局部聚合,再去掉前缀进行全局聚合。 - 启用 Spark 3.x Adaptive Query Execution (AQE):设置
spark.sql.adaptive.skewJoin.enabled = true,Spark 会自动检测倾斜的分区并进行拆分。
- 双重聚合(两阶段聚合):先对
- 解决方案:
右滑查看面试常问