如何按指定顺序在组中应用聚合?

时间:2017-06-22 03:15:43

标签: scala apache-spark apache-spark-sql

我的数据集如下:

+-------+-------------------+
|     id|                 ts|
+-------+-------------------+
|      b|2017-01-01 00:00:01|
|      b|2017-01-01 00:00:02|
|      b|2017-01-01 00:00:03|
|      b|2017-01-01 00:00:04|
|      b|2017-01-01 00:00:06|
|      b|2017-01-01 00:00:07|
|      d|2017-01-01 00:01:07|
|      d|2017-01-01 00:01:09|
|      d|2017-01-01 00:01:10|
|      d|2017-01-01 00:01:11|
|      d|2017-01-01 00:01:13|
|      d|2017-01-01 00:01:14|
+-------+-------------------+

我想在具有相同id的时间戳上应用聚合,并以递增的顺序对ts数据应用agg。 我所做的是使用udaf:

abstract class TsGroupAgg[OUT](f: (List[Long]) => OUT) extends 
Aggregator[Row, String, OUT] {
  def zero: String = ""
  def reduce(buffer: String, dataInGroup: Row): String =
    buffer + s";${dataInGroup.getString(1)}"

  def merge(b1: String, b2: String): String = s"$b1;$b2"

  def finish(r: String): OUT = {
    val list = r.split(";").toList
    f(list.filter(_.length > 0).map(DateUtils.getTimestamp))
  }

  def bufferEncoder: Encoder[String] = Encoders.STRING
}

def tsGrpCal: TypedColumn[Row, Int] =
      new TsGroupCnt(calculateGroupTs).toColumn.name("tsGrpCal")

df.groupBy("id").agg(tsGrpCal)

如您所见,我通过“id”将数据分组到数据框中,并应用我自己的聚合器。在我的聚合器中,我收集字符串中的所有ts数据,在最后一步中,我将字符串中的所有ts数据转换为列表,对其进行排序,并在列表中应用calculateGroupTs方法。在calculateGroupTs中,我可以按照asc的顺序应用聚合。 有一个问题,收集字符串中的所有ts数据不是一个好方法,它很难看。当数据量非常大,如1m时,就会导致OOM。 那么,有没有办法按顺序对分组数据应用聚合方法?

1 个答案:

答案 0 :(得分:1)

我想知道你为什么不使用开箱即用的Spark SQL中可用的窗口聚合函数,这会给你带来最佳性能?

  

有没有办法按顺序对分组数据应用聚合方法?

我是这么认为的。见下文。订单保证是输入订单,因此请根据您的需要对其进行排序并应用汇总。

val timeseries = spark.read.option("header", true).csv("timeseries.csv")
scala> timeseries.show
+---+-------------------+
| id|                 ts|
+---+-------------------+
|  b|2017-01-01 00:00:01|
|  b|2017-01-01 00:00:02|
|  b|2017-01-01 00:00:03|
|  b|2017-01-01 00:00:04|
|  b|2017-01-01 00:00:06|
|  b|2017-01-01 00:00:07|
|  d|2017-01-01 00:01:07|
|  d|2017-01-01 00:01:09|
|  d|2017-01-01 00:01:10|
|  d|2017-01-01 00:01:11|
|  d|2017-01-01 00:01:13|
|  d|2017-01-01 00:01:14|
+---+-------------------+

val tss = timeseries.groupBy("id").agg(collect_list("ts") as "tss")
scala> tss.show(false)
+---+------------------------------------------------------------------------------------------------------------------------------+
|id |tss                                                                                                                           |
+---+------------------------------------------------------------------------------------------------------------------------------+
|d  |[2017-01-01 00:01:07, 2017-01-01 00:01:09, 2017-01-01 00:01:10, 2017-01-01 00:01:11, 2017-01-01 00:01:13, 2017-01-01 00:01:14]|
|b  |[2017-01-01 00:00:01, 2017-01-01 00:00:02, 2017-01-01 00:00:03, 2017-01-01 00:00:04, 2017-01-01 00:00:06, 2017-01-01 00:00:07]|
+---+------------------------------------------------------------------------------------------------------------------------------+

val merged = tss.select($"id", concat_ws(";", $"tss") as "merge")
scala> merged.show(false)
+---+-----------------------------------------------------------------------------------------------------------------------+
|id |merge                                                                                                                  |
+---+-----------------------------------------------------------------------------------------------------------------------+
|d  |2017-01-01 00:01:07;2017-01-01 00:01:09;2017-01-01 00:01:10;2017-01-01 00:01:11;2017-01-01 00:01:13;2017-01-01 00:01:14|
|b  |2017-01-01 00:00:01;2017-01-01 00:00:02;2017-01-01 00:00:03;2017-01-01 00:00:04;2017-01-01 00:00:06;2017-01-01 00:00:07|
+---+-----------------------------------------------------------------------------------------------------------------------+

来自键入的API或使用自定义Aggregator的任何内容通常会导致较差的性能,而且我现在经常声称您使用的内置函数越多,性能就越好。

只需检查实际计划。

enter image description here

由于groupBy,我并不是说它是最好的实际计划,但在用例中使用自定义Scala代码可能会带来更糟糕的计划。