基于本文回答

播面 播面

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

给定订单明细表,计算每个用户在 2025 年期间,最长连续有多少天进行了下单购买

面试真题:计算 2025 年每个用户最长连续下单天数

题目描述
给定一张订单明细表 order_details,记录了用户的购买行为。请编写 SparkSQL 语句,计算出每个用户在 2025 年 期间,最长连续 有多少天进行了下单购买。


示例数据

订单明细表:order_details

order_id user_id order_date amount
o001 101 2024-12-31 100.0
o002 101 2025-01-01 150.0
o003 101 2025-01-02 200.0
o004 101 2025-01-03 50.0
o005 101 2025-01-05 300.0
o006 101 2025-01-06 120.0
o007 102 2025-01-01 80.0
o008 102 2025-01-01 90.0
o009 102 2025-01-02 110.0
o010 102 2025-01-04 150.0
o011 103 2025-01-01 200.0
o012 103 2025-01-03 250.0

样例说明:

  • 用户 101:在 2025 年的下单日期有 2025-01-01, 2025-01-02, 2025-01-03(连续 3 天)和 2025-01-05, 2025-01-06(连续 2 天)。2024-12-31 属于 2024 年,予以排除。因此,其最长连续天数为 3
  • 用户 102:在 2025-01-01 下了两次单(需去重),2025-01-02 下了一次单,累计连续 2 天。2025-01-04 下单 1 天。最长连续天数为 2
  • 用户 103:下单日期为 2025-01-012025-01-03,不连续。最长连续天数为 1

期望输出

user_id max_consecutive_days
101 3
102 2
103 1

SparkSQL 解决方案

sql
WITH temp_dedup AS (
    -- 1. 过滤 2025 年数据,并对【用户+日期】进行去重(同一天多次下单只算一天)
    SELECT DISTINCT 
        user_id, 
        CAST(order_date AS DATE) AS order_date
    FROM order_details
    WHERE order_date >= '2025-01-01' AND order_date <= '2025-12-31'
),
temp_rn AS (
    -- 2. 按照用户分组,日期升序排列,生成连续的序号
    SELECT 
        user_id,
        order_date,
        ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY order_date) AS rn
    FROM temp_dedup
),
temp_group AS (
    -- 3. 利用【日期 - 序号】差值相同的原理,构建连续下单的逻辑分组键 (diff_date)
    SELECT 
        user_id,
        order_date,
        DATE_SUB(order_date, rn) AS diff_date
    FROM temp_rn
),
temp_consecutive AS (
    -- 4. 统计每个用户在每个连续区间内的下单天数
    SELECT 
        user_id,
        diff_date,
        COUNT(1) AS consecutive_days
    FROM temp_group
    GROUP BY user_id, diff_date
)
-- 5. 聚合获取每个用户最长的连续下单天数
SELECT 
    user_id,
    MAX(consecutive_days) AS max_consecutive_days
FROM temp_consecutive
GROUP BY user_id;

核心考点与深度解析

在 SparkSQL / HiveSQL 面试中,“最长连续登录/下单天数”是一道经典的高频金牌题。考官通过此题主要考察候选人对窗口函数日期函数以及复杂逻辑抽象能力的掌握。

1. 核心解题思路:DATE_SUB(date, rn) 差值法

这是解决“连续性”问题的通用模板,其核心数学原理如下:

  • 如果日期是连续的(如:1号, 2号, 3号),而我们生成的窗口行号 row_number() 也是连续的(如:1, 2, 3)。
  • 当我们将 【日期 - 行号】 时,他们会得到一个完全相同的日期基准值(即 diff_date)。
  • 若日期发生中断(如:1号, 2号, 4号),对应的行号为 1, 2, 3,相减后的差值分别为 (31号, 31号, 1号)。此时差值不同,自然分成了不同的组。

图解说明(以用户 101 为例):

order_date rn DATE_SUB(order_date, rn) [diff_date] 逻辑含义
2025-01-01 1 2024-12-31 连续组 A
2025-01-02 2 2024-12-31 连续组 A
2025-01-03 3 2024-12-31 连续组 A
2025-01-05 4 2025-01-01 连续组 B
2025-01-06 5 2025-01-01 连续组 B

通过 GROUP BY user_id, diff_date,我们能轻易计算出 A 组有 3 天,B 组有 2 天,最后取 MAX 即可得到 3。

2. 面试易错防坑点(Deduplication)

  • 同一天多次下单去重:用户可能在同一天内多次下单(例如数据中的用户 102 在 2025-01-01 下单 2 次)。如果不做去重直接算 row_number(),行号会累加,从而导致差值计算错乱。因此,第一步必须进行 DISTINCTGROUP BY 去重
  • 严格的时间过滤:题目要求是“2025 年期间”。必须在第一步就切断跨年数据的干扰,例如本例中的 2024-12-31 必须被过滤掉。

3. SparkSQL 性能调优探讨(高分加分项)

在实际的 Spark 分布式生产环境中,直接运行上述 SQL 可能会遇到性能瓶颈。如果你能在面试中主动提及以下优化点,会极大增加通过率:

  • 数据倾斜(Data Skew)
    ROW_NUMBER() OVER(PARTITION BY user_id ...) 会导致相同 user_id 的所有数据被 Shuffle 到同一个 Reduce 任务中。如果存在“大卖家”或“热点用户”(某个 user_id 的订单量极大),就会导致 Executor OOM 或运行极慢。
    • 解决方案:如果只需计算连续天数,且已知数据源非常大,可以先将 user_id 加上随机前缀进行两阶段聚合,或者利用 Spark 3.x 的 Adaptive Query Execution (AQE) 自动倾斜处理。
  • 避免全表扫描
    temp_dedup 中,order_date 的过滤应该尽量利用分区剪裁(Partition Pruning)。如果 order_details 表是以 dt(日期)作为分区字段的,应该优先对分区字段进行过滤,避免扫描全表,从而大幅提升 Spark 读取 HDFS/S3 的 I/O 效率。