Apache spark spark聚合每个键的事件集,包括它们的更改时间戳

Apache spark spark聚合每个键的事件集,包括它们的更改时间戳,apache-spark,apache-spark-sql,user-defined-functions,aggregation,window-functions,Apache Spark,Apache Spark Sql,User Defined Functions,Aggregation,Window Functions,对于以下数据帧: +----+--------+-------------------+----+ |user| dt| time_value|item| +----+--------+-------------------+----+ | id1|20200101|2020-01-01 00:00:00| A| | id1|20200101|2020-01-01 10:00:00| B| | id1|20200101|2020-01-01 09:00:00|

对于以下数据帧:

+----+--------+-------------------+----+
|user|      dt|         time_value|item|
+----+--------+-------------------+----+
| id1|20200101|2020-01-01 00:00:00|   A|
| id1|20200101|2020-01-01 10:00:00|   B|
| id1|20200101|2020-01-01 09:00:00|   A|
| id1|20200101|2020-01-01 11:00:00|   B|
+----+--------+-------------------+----+
我想捕获所有唯一的项目,即
收集集
,但保留其自己的
时间值

import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.col
import org.apache.spark.sql.functions.unix_timestamp
import org.apache.spark.sql.functions.collect_set
import org.apache.spark.sql.types.TimestampType
val timeFormat = "yyyy-MM-dd HH:mm"
val dx = Seq(("id1", "20200101", "2020-01-01 00:00", "A"), ("id1", "20200101","2020-01-01 10:00", "B"), ("id1", "20200101","2020-01-01 9:00", "A"), ("id1", "20200101","2020-01-01 11:00", "B")).toDF("user", "dt","time_value", "item").withColumn("time_value", unix_timestamp(col("time_value"), timeFormat).cast(TimestampType))
dx.show
A

当信号从A切换到B时,不保留时间值信息。如何保留项目中每个集合的时间值信息

是否可以在窗口功能中设置collect_以达到预期的结果?目前,我只能想到:

  • 使用窗口函数确定事件对
  • 筛选以更改事件
  • 聚合
  • 需要多次洗牌。或者,可以使用UDF(
    collect\u list(sort\u array(struct(time\u value,item)))
    ),但这似乎也相当笨拙


    有更好的方法吗?

    我确实会使用窗口函数来隔离更改点,我认为没有其他选择:

    val win = Window.partitionBy($"user",$"dt").orderBy($"time_value")
    
    dx
    .orderBy($"time_value")
    .withColumn("item_change_post",coalesce((lag($"item",1).over(win)=!=$"item"),lit(false)))
    .withColumn("item_change_pre",lead($"item_change_post",1).over(win))
    .where($"item_change_pre" or $"item_change_post")
    .show()
    
    +----+--------+-------------------+----+----------------+---------------+
    |user|      dt|         time_value|item|item_change_post|item_change_pre|
    +----+--------+-------------------+----+----------------+---------------+
    | id1|20200101|2020-01-01 09:00:00|   A|           false|           true|
    | id1|20200101|2020-01-01 10:00:00|   B|            true|          false|
    +----+--------+-------------------+----+----------------+---------------+
    
    然后使用类似于
    groupBy($“user”,$“dt”).agg(collect\u list(struct($“time\u value”,$“item”))

    我认为不会发生多次洗牌,因为您总是使用相同的键进行分区/分组


    您可以尝试通过将每个
    项目的初始数据帧聚合到最小/最大
    时间值
    ,然后执行上述操作,从而提高效率。

    我确实会使用窗口函数来隔离更改点,我认为没有其他选择:

    val win = Window.partitionBy($"user",$"dt").orderBy($"time_value")
    
    dx
    .orderBy($"time_value")
    .withColumn("item_change_post",coalesce((lag($"item",1).over(win)=!=$"item"),lit(false)))
    .withColumn("item_change_pre",lead($"item_change_post",1).over(win))
    .where($"item_change_pre" or $"item_change_post")
    .show()
    
    +----+--------+-------------------+----+----------------+---------------+
    |user|      dt|         time_value|item|item_change_post|item_change_pre|
    +----+--------+-------------------+----+----------------+---------------+
    | id1|20200101|2020-01-01 09:00:00|   A|           false|           true|
    | id1|20200101|2020-01-01 10:00:00|   B|            true|          false|
    +----+--------+-------------------+----+----------------+---------------+
    
    然后使用类似于
    groupBy($“user”,$“dt”).agg(collect\u list(struct($“time\u value”,$“item”))

    我认为不会发生多次洗牌,因为您总是使用相同的键进行分区/分组


    您可以尝试将每个
    项目的初始数据帧聚合到最小/最大
    时间值
    ,以提高效率,然后执行上述操作。

    您的预期结果是什么?在当前聚合中,A和B都有两个不同的“时间值”候选项,应该选择哪一个?正如@Lamanus指出的,很难推断出你的最终目标是什么。你的预期结果是什么?在你当前的汇总中,A和B都有两个不同的“时间价值”候选项,应该选择哪一个?正如@Lamanus所指出的,很难推断你的最终目标是什么。但是当我想以一组边界结束时,第二次聚合是必要的。但是当我想以一组边界结束时,第二次聚合是必要的。