SparkSql补充缺失日期和数据

该文章介绍了如何在SparkSQL中使用窗口函数lead来获取每个商品的下一个日期,然后通过自定义函数或split结合explode方法生成日期序列,以填补日期间的空白,确保每天都有商品价格记录。

问题:

补充缺失日期,其他数值,按照上一个有值的数据补充

表结构

sku_name string 商品名称

dt 日期

sku_amount 商品价格

表数据

    sc.makeRDD(Seq(
      ("iphone", "2023-02-03", 100),
      ("iphone", "2023-02-05", 300),
      ("iphone", "2023-02-08", 150),
      ("mac", "2023-02-01", 200),
      ("mac", "2023-02-02", 400),
      ("mac", "2023-02-06", 700),
      ("airpods", "2023-02-02", 300),
      ("airpods", "2023-02-04", 200),
      ("airpods", "2023-02-07", 100),
      ("airpods", "2023-02-11", 400)
    )).toDF("sku_name", "dt", "sku_amount").createOrReplaceTempView("product")

期望输出

思路

1:利用窗口函数lead,补充同一组的下一个日期

2:根据当前的日期和和补充的下一个日期,利用space/split等函数构建posexplde数据展开

3:利用date_add函数和posexplode函数展开的索引获取相应日期,且把本行的amount数据补充上

    spark.sql(
      """
        |select
        |    sku_name,
        |    date_add(dt,col_idx) dt,
        |    sku_amount
        |from
        |(
        |    select
        |        sku_name,
        |        dt,
        |        sku_amount,
        |        lead(dt,1,dt) over(partition by sku_name order by dt) next_dt
        |    from
        |    product ) tmp
        |    lateral view posexplode (
        |        split (space( datediff(next_dt, dt)), ' (?!$)')
        |) tbl_idx AS col_idx,col_val;
        |""".stripMargin).show(100, false)

拓展

基于以上思路,有时候使用posexplode,space有时候不好理解,简单补充一下思路

  1. 利用窗口函数lead,补充同一组的下一个日期

  1. 直接使用自定义函数,将dt到next_dt函数补齐就好

def splitDateFun(startDay:String,endDay:String) = {
    //含头不含尾的buffer
    val buffer = new ArrayBuffer[String]()
    val sdf = new SimpleDateFormat("yyyy-MM-dd")
    val startDate = sdf.parse(startDay)
    val endDate = sdf.parse(endDay)
    val calBegin = Calendar.getInstance()
    calBegin.setTime(startDate)
    buffer += startDay
    while (endDate.after(calBegin.getTime)){
      calBegin.add(Calendar.DAY_OF_MONTH,1)
      val curr = sdf.format(calBegin.getTime)
      if(!curr.equals(endDay)){
        buffer += curr
      }
    }
    buffer
  }
spark.udf.register("spalitdatefun",splitDateFun _)
    spark.sql(
      """
        |select
        |sku_name,
        |col_val,
        |sku_amount
        |from
        |(
        |    select
        |        sku_name,
        |        dt,
        |        sku_amount,
        |        lead(dt,1,dt) over(partition by sku_name order by dt) next_dt
        |    from
        |    product ) tmp
        |    lateral view explode (
        |        spalitdatefun(dt,next_dt)
        |) tbl_idx AS col_val;
        |""".stripMargin).show(100, false)

完整代码

import org.apache.spark.sql.SparkSession

import java.text.SimpleDateFormat
import java.util.Calendar
import scala.collection.mutable.ArrayBuffer

object Test1 {


  def main(args: Array[String]): Unit = {

    val spark = SparkSession
      .builder()
      .appName("SparkSessionTest")
      .master("local")
      .getOrCreate()
    val sc = spark.sparkContext
    import spark.implicits._

    sc.makeRDD(Seq(
      ("iphone", "2023-02-03", 100),
      ("iphone", "2023-02-05", 300),
      ("iphone", "2023-02-08", 150),
      ("mac", "2023-02-01", 200),
      ("mac", "2023-02-02", 400),
      ("mac", "2023-02-06", 700),
      ("airpods", "2023-02-02", 300),
      ("airpods", "2023-02-04", 200),
      ("airpods", "2023-02-07", 100),
      ("airpods", "2023-02-11", 400)
    )).toDF("sku_name", "dt", "sku_amount").createOrReplaceTempView("product")


    spark.sql(
      """
        |select
        |    sku_name,
        |    date_add(dt,col_idx) dt,
        |    sku_amount
        |from
        |(
        |    select
        |        sku_name,
        |        dt,
        |        sku_amount,
        |        lead(dt,1,dt) over(partition by sku_name order by dt) next_dt
        |    from
        |    product ) tmp
        |    lateral view posexplode (
        |        split (space( datediff(next_dt, dt)), ' (?!$)')
        |) tbl_idx AS col_idx,col_val;
        |""".stripMargin).show(100, false)

    spark.udf.register("spalitdatefun",splitDateFun _)
    spark.sql(
      """
        |select
        |sku_name,
        |col_val,
        |sku_amount
        |from
        |(
        |    select
        |        sku_name,
        |        dt,
        |        sku_amount,
        |        lead(dt,1,dt) over(partition by sku_name order by dt) next_dt
        |    from
        |    product ) tmp
        |    lateral view explode (
        |        spalitdatefun(dt,next_dt)
        |) tbl_idx AS col_val;
        |""".stripMargin).show(100, false)
    sc.stop()
    spark.stop()
  }

  def splitDateFun(startDay:String,endDay:String) = {
    //含头不含尾的buffer
    val buffer = new ArrayBuffer[String]()
    val sdf = new SimpleDateFormat("yyyy-MM-dd")
    val startDate = sdf.parse(startDay)
    val endDate = sdf.parse(endDay)
    val calBegin = Calendar.getInstance()
    calBegin.setTime(startDate)
    buffer += startDay
    while (endDate.after(calBegin.getTime)){
      calBegin.add(Calendar.DAY_OF_MONTH,1)
      val curr = sdf.format(calBegin.getTime)
      if(!curr.equals(endDay)){
        buffer += curr
      }
    }
    buffer
  }
}

参考:https://mp.weixin.qq.com/s/05r_XJ9CjnJFyeZU3TChCw

### SparkSQL HiveSQL 数据存储机制及区别 #### 1. 存储格式的支持 SparkSQL HiveSQL 都支持多种常见的大数据存储格式,例如 Parquet、ORC、Avro、JSON 等。然而,在实际应用中,两者的实现方式存在差异。Hive 更加依赖于 HDFS 文件系统的原生存储能力,而 SparkSQL 则通过 DataFrames 提供了一种更灵活的方式处理不同数据源[^2]。 #### 2. 默认存储行为 Hive 使用的是基于 HDFS 的纯文件存储模式,默认情况下会将表数据保存为文本文件或其他特定格式(如 ORC 或 Parquet)。相比之下,SparkSQL 并不直接管理底层存储,而是可以对接不同的外部存储系统(如 HDFS、S3、Alluxio),并利用其自身的内存计算优势加速查询过程[^1]。 #### 3. 查询优化与缓存策略 当涉及到复杂查询时,两者对于中间结果的持久化有不同的做法。Hive 将所有的临时数据都写回到磁盘上;相反地,Spark 可以选择性地把部分或者全部的结果集保留在内存之中以便后续重用,从而减少 I/O 开销。这种设计使得 Spark 在迭代算法以及交互式分析场景下表现得更为出色[^3]。 #### 4. 元数据管理 尽管二者都能够操作相同的物理数据文件,但在元数据层面却有着显著的不同之处。传统意义上的 Hive 表定义及其属性信息被记录在一个独立的关系型数据库实例当中(比如 MySQL 或 PostgreSQL),称为 Metastore 。如果希望 Spark 能够识别已有的 Hive 表,则需要开启相应选项来共享同一个 metastore 实例。 ```sql SET spark.sql.catalogImplementation=hive; ``` 上述命令允许 Spark 访问现有的 Hive 表结构而不必重新创建它们。 --- ### 示例代码展示如何设置兼容性参数 以下是调整 SparkSQL 参数使其更好地适配 Hive 环境的例子: ```scala // 设置 SparkSession 来启用 Hive 支持 val spark = SparkSession.builder() .appName("ExampleApp") .enableHiveSupport() // 启动对 Hive 的支持功能 .getOrCreate() // 修改配置项以确保一致性 spark.conf.set("spark.sql.hive.convertMetastoreOrc", "false") // 关闭内置 ORC 解析器 spark.conf.set("spark.sql.hive.convertMetastoreParquet", "false") // 关闭内置 PARQUET 解析器 spark.conf.set("hive.mapred.supports.subdirectories", "true") // 允许子目录作为输入路径的一部分 spark.conf.set("mapreduce.input.fileinputformat.input.dir.recursive", "true") // 同样作用于 MapReduce 输入格式类 ``` 以上脚本片段展示了怎样通过对某些关键变量赋值达到使 SparkSQL 完全遵循 Hive 原始语义的目的。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值