基于本文回答
0
评论

将一天 24 小时划分为四个时段:深夜(0-6)、上午(6-12)、下午(12-18)、晚上(18-24)。统计各时段的下单用户数、下单订单数以及 GMV 的占比

面试题:全天时段消费行为分析

题目描述

在电商和本地生活业务中,分析用户在一天中不同时段的消费习惯对于精细化运营至关重要。请编写 SparkSQL 查询,将一天 24 小时划分为四个时段:

  • 深夜([0, 6) 点)
  • 上午([6, 12) 点)
  • 下午([12, 18) 点)
  • 晚上([18, 24) 点)

统计各时段的下单用户数(去重)、下单订单数以及 GMV 的占比


1. 基础数据准备

订单表:t_order
order_id user_id order_time gmv
ord_001 U1 2023-10-25 02:30:00 100.0
ord_002 U2 2023-10-25 05:15:00 150.0
ord_003 U1 2023-10-25 08:45:00 200.0
ord_004 U3 2023-10-25 10:30:00 300.0
ord_005 U4 2023-10-25 14:20:00 50.0
ord_006 U4 2023-10-25 15:40:00 250.0
ord_007 U5 2023-10-25 19:10:00 400.0
ord_008 U2 2023-10-25 21:00:00 150.0

2. SparkSQL 核心解法

sql
WITH temp_order_period AS (
    SELECT 
        order_id,
        user_id,
        gmv,
        -- 提取小时数
        HOUR(order_time) AS order_hour
    FROM t_order
),
temp_period_mapped AS (
    SELECT 
        order_id,
        user_id,
        gmv,
        CASE 
            WHEN order_hour >= 0 AND order_hour < 6   THEN '深夜'
            WHEN order_hour >= 6 AND order_hour < 12  THEN '上午'
            WHEN order_hour >= 12 AND order_hour < 18 THEN '下午'
            WHEN order_hour >= 18 AND order_hour < 24 THEN '晚上'
            ELSE '未知'
        END AS time_period
    FROM temp_order_period
)
SELECT 
    time_period,
    -- 1. 下单用户数(去重)
    COUNT(DISTINCT user_id) AS active_user_count,
    -- 2. 下单订单数
    COUNT(order_id) AS total_order_count,
    -- 3. 各时段 GMV
    SUM(gmv) AS period_gmv,
    -- 4. 占比计算:当前时段GMV / 总GMV(利用窗口函数避免二次关联)
    ROUND(SUM(gmv) / SUM(SUM(gmv)) OVER() * 100, 2) AS gmv_ratio_pct
FROM temp_period_mapped
GROUP BY time_period
ORDER BY 
    CASE time_period
        WHEN '深夜' THEN 1
        WHEN '上午' THEN 2
        WHEN '下午' THEN 3
        WHEN '晚上' THEN 4
        ELSE 5
    END;

3. 预期输出结果

time_period active_user_count total_order_count period_gmv gmv_ratio_pct
深夜 2 2 250.0 15.63
上午 2 2 500.0 31.25
下午 1 2 300.0 18.75
晚上 2 2 550.0 34.38

4. 面试必会:SparkSQL 深度解析与调优

核心考点 1:窗口函数与聚合函数的嵌套使用 SUM(SUM(x)) OVER()
  • 原理解析:在计算 GMV 占比时,新手常会使用 子查询先求SUM(gmv),然后再用主查询 JOIN 起来。
  • 高级写法SUM(SUM(gmv)) OVER()
    • 内层的 SUM(gmv) 是基于 GROUP BY time_period 聚合后的组内 GMV
    • 外层的 SUM(...) OVER() 是一个无 PARTITION BY 的全局窗口函数,它将四个时段的 GMV 再次求和,得到全局总 GMV
    • 优势:避免了额外的 JOIN 算子,减少了 Shuffle 阶段,执行效率大幅提升。
核心考点 2:时间函数的选择与物理执行
  • HOUR(order_time) 是 SparkSQL 内置的高效时间提取函数。
  • 如果面试官追问:“如果 order_time 是 String 类型而非 Timestamp,该如何处理?”
    • :可以使用 CAST(order_time AS TIMESTAMP) 进行隐式转换,或者直接使用 substring(order_time, 12, 2) 提取小时字符并转为整型,后者在特定场景下甚至能省去时间解析的开销。
核心考点 3:数据倾斜与优化(高频面试追问)
  • 问题:如果数据量极大,且“晚上”时段的订单量占了 90%,如何优化此 SQL?
  • 优化方案
    1. Count(Distinct) 优化COUNT(DISTINCT user_id) 会引入全局 Shuffle。如果数据量巨大,可以开启 Spark 的两阶段聚合参数 spark.sql.adaptive.skewJoin.enabled,或者通过手动加盐法(给 user_id 加随机前缀进行局部聚合,再去掉前缀进行全局聚合)来分散热点。
    2. 过滤不必要的数据:在 WHERE 条件中提前过滤掉无用的异常数据或未支付订单,减少参与计算的数据量。
右滑查看面试常问