SparkSQL如何优化最近N天下单金额最大值

需求背景

已知用户订单金额表user_order_amt_1d,计算最近N天用户下单金额最大值(N = 3、30、180)

user_id(用户id)

amt(当日下单金额)

dt(时间分区)

001

10000

20251101

002

20000

20251101

003

30000

20251101

001

1000

20251102

002

20000

20251102

003

35000

20251102

.........

......

......

初级解决方案

  • 这个需求本身并不难,只需分别求出最近3天用户金额最大值最近30天用户金额最大值最近180用户金额最大值,然后进行关联即可
spark.sql(
  s"""
     |select
     |  t1.user_id,
     |  t1.max_amt_3d,
     |  t2.max_amt_30d,
     |  t3.max_amt_180d
     |from (
     |select user_id, max(amt) as max_amt_3d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-2}'
     |group by user_id
     |) t1
     |JOIN (
     |select user_id, max(amt) as max_amt_30d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-29}'
     |group by user_id
     |) t2
     |on t1.user_id = t2.user_id
     |JOIN (
     |select user_id, max(amt) as max_amt_180d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-179}'
     |group by user_id
     |) t3
     |on t1.user_id = t3.user_id
     |""".stripMargin).show

存在的问题

1、【数据量大】同一个数据源被读取了多次,数据量剧增

2、【Shuffle多】多张表之间JOIN,存在多次Shuffle

3、【扩展性差】如果业务需要计算最近90天或者360天用户下单金额最大值时,需重新修改代码

进阶解决方案

  • 针对上述问题1和问题2,对SQL可进行如下优化:
spark.sql(
  s"""
     |select
     |  user_id,
     |  max(case when dt >= '${dt-2}' then amt else 0 end) as max_amt_3d,
     |  max(case when dt >= '${dt-29}' then amt else 0 end) as max_amt_30d,
     |  max(case when dt >= '${dt-179}' then amt else 0 end) as max_amt_180d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-179}'
     |group by user_id
     |""".stripMargin).show

存在的问题

  • 此方案,数据源只会被读取一次,同时避免了多张表的JOIN操作,但是 user_order_amt_1d是亿级用户表,我们每日扫描最近180天的用户数据,显然读取的数据量仍然很大,进行shuffle仍然效率低下

终极解决方案

  • 如何避免每日读取最近180天的用户数据?
  • 我们可以保存昨日计算的状态,然后和今日数据进行合并,具体操作如下:
  • 设计一个用户下单金额累计表,存储最近180天下单金额的数组
  • 使用array_max函数+slice函数即可获取最近N天下单金额中的最大值(N <= 180)
// 1、存储最近180天下单金额
spark.sql(
  s"""
    |insert overwrite table user_order_amt_td partition(dt = '$dt')
    |select
    | t1.user_id,
    | case when size(t1.amt_180d_arr) >= 180 then concat(array(t2.amt), slice(t1.amt_180d_arr, 1, 179))
    |   else concat(array(t2.amt), t1.amt_180d_arr)  end as amt_180d_arr
    |from (
    |select user_id, amt_180d_arr
    |from user_order_amt_nd
    |where dt = '${dt-1}
    |) t1
    |join (
    |select user_id, amt
    |from user_order_amt_1d
    |where dt = '$dt'
    |) t2
    |on t1.user_id = t2.user_id
    |""".stripMargin).show
// 2、求解最近3、30、180天下单金额最大值
spark.sql(
  s"""
    |select
    | user_id,
    | array_max(slice(amt_180d_arr, 1, 3)) as max_amt_3d,
    | array_max(slice(amt_180d_arr, 1, 30)) as max_amt_30d,
    | array_max(amt_180d_arr) as max_amt_180d
    |from user_order_amt_td
    |where dt = '$dt'
    |""".stripMargin).show

思考

以上三种方案都未能解决扩展性差的问题,我们是否可以在不改动业务代码的前提下,完成近N天用户下单金额最大值的扩展呢?(N为任意值)

如果当前架构解决不了,那就加一张表,假设还解决不了,怎么办?

下期我会详细分享如何通过配置化的方式来求解最近N天下单金额最大值

#面试问题记录##数据人的面试交流地#
大数据开发面试笔记 文章被收录于专栏

包括大数据篇、计算机语言篇、计算机基础篇、算法刷题篇、面试经验篇等五大篇章: 大数据篇包括框架原理、源码解析、调优技巧、大数据场景题、项目实战、数仓理论等模块;计算机语言篇包括Java、Linux、大厂常考SQL面试题等模块;计算机基础篇包括计算机网络、操作系统、数据库、数据结构等模块;算法刷题篇包括大厂高频算法题、刷题速成计划等模块 面试经验篇包括BAT、美团、字节、快手、京东等大厂的面经合集

全部评论
这个原题是再哪
点赞 回复 分享
发布于 2025-12-12 20:14 陕西
mark
点赞 回复 分享
发布于 2025-12-12 10:46 北京

相关推荐

01-12 14:08
门头沟学院 Java
有寒假来武汉小米总部实习的大学生嘛,我也是小米的员工,想找合租舍友,仅限女生可免租半月,二月初可入住,也就是说房租是2.15开始算的哦~也可以将行李提前放过来~房屋介绍:1、房子情况:有电梯;租的是三室一厅一卫一厨,&nbsp;但是有个卧室比较小,不打算找人,只住两个人就可以了;衣柜也很大,可以放下很多衣服;房屋采光真的很好,早上起来可以在床上晒太阳的那种,十分惬意(夏季晚上十分好看!)2.&nbsp;楼下离我们很近的地方有小吃街和一个两层大超市(大概步行两分钟多就可以走到)&nbsp;,还有一个新开的麦当劳,晚上可以去吃小吃,购买物资也可以去大超市;3.&nbsp;房子基本设施齐备(洗衣机,冰箱,空调,油烟机,热水器);4.&nbsp;我有稳定的工作,生活中很注意卫生,周末有时间会自己做饭,可以投喂哦~5.&nbsp;出行:距离公交站步行10分钟不到,距政务中心,武汉小米总部三站(晚上我都是走回来的,很近的~);一个比较进的地铁,距离大概1km左右;出入我觉得很方便;6.&nbsp;房租:1150每月,押一付二,无物业费,也没有中介费和其他额外费用。7.&nbsp;民用水电燃气,用多少交多少,水电费正常平摊。希望你是:1.&nbsp;女生(本人女),不带异性回家,如有同性朋友来玩,最多过夜一晚;2.&nbsp;爱干净,讲卫生,作息正常,不吵闹,有稳定工作;3.&nbsp;好沟通,有任何问题一定要沟通,不要闷着!中介勿扰,非诚勿扰!!!希望不要浪费彼此的时间诚心有意向的可以联系我看房
租房找室友
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务