基于本文回答
0
评论

统计平台大促期间发放的“首小时免单券”、“寄件立减券”的核销率(已使用数量 / 已领取数量),并计算抵扣的累计总成本

SparkSQL 面试题:大促优惠券核销率与成本分析

在物流与电商平台的大促活动中,优惠券的核销情况和成本控制是运营团队关注的核心指标。请根据以下给定的数据表,编写 SparkSQL 查询,并进行性能优化分析。


一、 场景数据准备

1. 优惠券维度表 (dim_coupon)

记录优惠券的基本信息。

coupon_id (券ID) coupon_name (券名称) coupon_type (券类型) discount_amount (抵扣金额/元)
C001 首小时免单券 免单券 20.00
C002 寄件立减券 立减券 5.00
C003 满减神券 满减券 15.00

2. 优惠券领取与使用明细表 (fact_coupon_usage)

记录用户领取和使用优惠券的流水信息(status:1-已领取未用,2-已使用)。

record_id (记录ID) user_id (用户ID) coupon_id (券ID) status (状态) receive_time (领取时间) use_time (使用时间)
R001 U1001 C001 2 2023-11-11 00:05:00 2023-11-11 00:20:00
R002 U1002 C001 1 2023-11-11 00:15:00 NULL
R003 U1003 C002 2 2023-11-11 09:00:00 2023-11-11 10:30:00
R004 U1004 C002 2 2023-11-11 10:00:00 2023-11-11 11:15:00
R005 U1005 C003 2 2023-11-11 12:00:00 2023-11-11 14:00:00
R006 U1006 C002 1 2023-11-11 15:00:00 NULL

二、 面试需求

编写一个 SparkSQL 语句,统计“首小时免单券”“寄件立减券”两种券的:

  1. 已领取数量received_cnt
  2. 已使用(核销)数量used_cnt
  3. 核销率write_off_rate,保留两位小数,展现为百分比形式,如 50.00%
  4. 抵扣的累计总成本total_cost,即已使用券的抵扣总金额)

三、 核心代码实现 (SparkSQL)

sql
SELECT 
    d.coupon_name,
    COUNT(1) AS received_cnt,
    SUM(CASE WHEN f.status = 2 THEN 1 ELSE 0 END) AS used_cnt,
    -- 计算核销率:已使用 / 已领取
    CONCAT(
        CAST(
            ROUND(
                SUM(CASE WHEN f.status = 2 THEN 1.0 ELSE 0.0 END) / COUNT(1) * 100, 
                2
            ) AS DECIMAL(10, 2)
        ), 
        '%'
    ) AS write_off_rate,
    -- 累计总成本:仅计算已使用的券金额
    SUM(CASE WHEN f.status = 2 THEN d.discount_amount ELSE 0.0 END) AS total_cost
FROM fact_coupon_usage f
JOIN dim_coupon d ON f.coupon_id = d.coupon_id
WHERE d.coupon_name IN ('首小时免单券', '寄件立减券')
GROUP BY d.coupon_name;

预期输出结果:

coupon_name received_cnt used_cnt write_off_rate total_cost
首小时免单券 2 1 50.00% 20.00
寄件立减券 3 2 66.67% 10.00

四、 SparkSQL 深度分析与面试加分项

在面试中,仅仅写出 SQL 只能拿到基础分。若能主动从 Spark 执行引擎机制性能优化 角度进行以下分析,将极大提升面试通过率:

1. Join 优化:广播连接(Broadcast Hash Join)

  • 分析:在本题中,维度表 dim_coupon(优惠券配置表)数据量通常极小(几百到几千条),而事实表 fact_coupon_usage(领取与使用流水表)在双十一大促期间数据量可能达到数亿级。
  • 优化方案:避免使用默认的 Shuffle Hash Join,通过显式提示(Hint)或配置将维度表广播到各个 Executor,消除 Shuffle 阶段,提升数倍性能。
    sql
    -- SparkSQL 广播优化写法
    SELECT /*+ BROADCAST(d) */ 
        d.coupon_name, ...
    FROM fact_coupon_usage f
    JOIN dim_coupon d ON f.coupon_id = d.coupon_id

2. 避免数据倾斜(Data Skew)

  • 分析:大促期间,“首小时免单券”等爆款券的设计会导致 fact_coupon_usage 表中某些 coupon_id 的记录数远超其他券,Join 和 GROUP BY 时极易发生数据倾斜(某个 Task 运行极慢)。
  • 解决方案
    1. 过滤前置:在 Join 之前,先通过 WHERE d.coupon_name IN (...) 过滤维度表,Spark 的 Catalyst 优化器通常会进行谓词下推(Predicate Pushdown),减少参与 Join 的数据量。
    2. 加盐法(Salting):如果依然发生严重倾斜,可以对事实表的 coupon_id 加上随机前缀(如 0_C001, 1_C001),将维度表复制多份进行 Join,打散数据后再聚合。

3. 聚合优化(Aggregate Performance)

  • 分析:SQL 中使用了 SUM(CASE WHEN...) 配合 GROUP BY
  • 优化机制:SparkSQL 会将其转化为 HashAggregate 算子。在 Shuffle 写入前,Spark 会在 Map 端先进行本地预聚合(类似于 MapReduce 的 Combiner),极大地减少了网络传输的数据量。无需担心数据量大导致 Shuffle 撑爆内存。

4. 精度损失防范

  • 分析:在计算成本 total_cost 和核销率时,直接使用 FLOATDOUBLE 会因为二进制浮点数表示问题导致精度丢失(例如 0.1 + 0.2 = 0.30000000000000004)。
  • 避坑指南:在处理金额和比例时,必须使用 DECIMAL(precision, scale) 类型进行转换,确保财务数据的准确性。
右滑查看面试常问