找出在同一个订单(或购物车)中经常被同时购买的商品对(Product_A, Product_B)的频次,按组合频次降序排列
面试题:电商购物车/订单商品关联分析
题目描述:
在推荐系统和购物篮分析(Market Basket Analysis)中,找出经常被一起购买的商品组合是非常经典的场景。现在有一张订单商品明细表 order_items,请编写 SparkSQL 查询,找出在同一个订单中经常被同时购买的商品对(Product_A, Product_B)的频次,并按组合频次降序排列。如果频次相同,则按商品 A 的名称升序、商品 B 的名称升序排列。
注意:
- 避免重复组合:如 (苹果, 香蕉) 和 (香蕉, 苹果) 视为同一种组合,只保留一种(通常排序靠前的在 A,靠后的在 B)。
- 避免自身组合:商品不能和自己组成一对(如 苹果, 苹果)。
数据准备
1. 输入表:order_items(订单明细表)
| order_id (订单ID) | product_id (商品ID) | product_name (商品名称) |
|---|---|---|
| 1001 | P01 | 苹果 (Apple) |
| 1001 | P02 | 香蕉 (Banana) |
| 1001 | P03 | 樱桃 (Cherry) |
| 1002 | P01 | 苹果 (Apple) |
| 1002 | P02 | 香蕉 (Banana) |
| 1003 | P01 | 苹果 (Apple) |
| 1003 | P03 | 樱桃 (Cherry) |
| 1004 | P02 | 香蕉 (Banana) |
| 1004 | P04 | 榴莲 (Durian) |
期望输出
| product_a | product_b | frequency (购买频次) |
|---|---|---|
| 苹果 (Apple) | 香蕉 (Banana) | 2 |
| 苹果 (Apple) | 樱桃 (Cherry) | 2 |
| 香蕉 (Banana) | 榴莲 (Durian) | 1 |
| 香蕉 (Banana) | 樱桃 (Cherry) | 1 |
SparkSQL 参考答案
sql
SELECT
a.product_name AS product_a,
b.product_name AS product_b,
COUNT(1) AS frequency
FROM order_items a
JOIN order_items b
ON a.order_id = b.order_id
AND a.product_id < b.product_id
GROUP BY
a.product_name,
b.product_name
ORDER BY
frequency DESC,
product_a ASC,
product_b ASC;
SparkSQL 深度解析与面试官加分项
在实际的 Spark 岗位面试中,仅仅写出上述 SQL 是不够的。面试官往往会针对这个 SQL 提出一系列深入的性能优化问题。以下是针对该题目的 Spark 核心考点与优化分析:
1. 核心考点:自关联(Self-Join)与去重技巧
- 非等值连接(Non-Equi Join)去重:
通过a.product_id < b.product_id作为连接条件,一举解决了两个问题:- 避免了商品自我组合(排除了
a.product_id = b.product_id)。 - 避免了重复的排列组合(排除了
(P02, P01),只保留(P01, P02)),从而将排列(Permutation)问题简化为了组合(Combination)问题,数据量直接减半。
- 避免了商品自我组合(排除了
2. Spark 性能隐患:数据膨胀(Data Explosion)
- 痛点:
自关联会产生 的数据膨胀。如果某个热点订单(例如:大促期间的批发商订单)包含 个商品,自关联后该订单会产生 条数据。如果 ,一个订单就会膨胀出近 50 万条数据,极易导致 OOM(内存溢出) 或 Executor 运行缓慢。 - 面试加分方案(主动向面试官提及):
- 限制单订单商品数: 在 Join 之前,过滤掉订单中商品数异常庞大的订单(如
COUNT(product_id) > 50的订单,这通常是爬虫或异常商家)。 - 高频商品过滤(剪枝): 实际业务中,可以先统计单品词频,过滤掉极低频的商品,只对高频商品进行关联分析。
- 限制单订单商品数: 在 Join 之前,过滤掉订单中商品数异常庞大的订单(如
3. Spark Shuffle 优化与倾斜(Data Skew)
- 倾斜原因:
当使用GROUP BY a.product_name, b.product_name时,Spark 会根据这两个字段进行 Hash Shuffle。如果某对商品(如“苹果+香蕉”)是爆款,该 Key 对应的数据会被分发到同一个 Reduce Task 中,导致该 Task 处理时间过长,出现数据倾斜。 - 优化对策:
- 两阶段聚合(加盐局部聚合):
先给 Group By 的 Key 加上随机前缀进行局部聚合,再去掉前缀进行全局聚合,分散单点压力。 - 广播连接(Broadcast Join):
如果order_items表本身经过过滤后尺寸极小(例如限制了只分析某天的数据),可以考虑将其中一份数据广播出去,将 SortMergeJoin 转化为 BroadcastHashJoin,彻底避免 Shuffle。
- 两阶段聚合(加盐局部聚合):
4. 架构思维扩展:Spark MLlib 替代方案
- 面试官可能会问: “如果数据量达到数十亿,SQL 自关联完全跑不动,你有什么其他思路?”
- 标准回答:
在海量数据下,利用 SQL 进行双表关联求解频繁项集效率极低。推荐使用 Spark MLlib 库中封装好的 FP-Growth(频繁模式增长)算法 或 PrefixSpan 算法。这些算法采用树形结构(FP-Tree)存储数据,只需扫描两次数据库,无需显式进行 的自关联,是大数据场景下处理购物篮分析的标准解决方案。
右滑查看面试常问