通过重叠列

时间:2018-03-08 20:40:56

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

我有一个数据集(“guid”,“timestamp”,“agt”),如下所示

val df = List(Test("a", "1", null),
   Test("b", "2", "4"),
   Test("a", "1", "3"),
   Test("b", "2", "4"),
   Test("c", "1", "3"),
   Test("a", "6", "8"),
   Test("b", "2", "4"),
   Test("a", "1", "4")

我需要计算

  • 按guid分组时每行的最小时间戳。
  • 按(guid,timestamp)
  • 分组时每个键的计数
  • 按guid分组并按时间戳(desc)排序时的行的agtM,然后取第一个非空的agt else“”
  • 删除重复项

所以输出如下所示。

+----+---------+---+-------+-----+----+
|guid|timestamp|agt|minimum|count|agtM|
+----+---------+---+-------+-----+----+
|   c|        1|  3|      1|    1|   3|
|   b|        2|  4|      2|    3|   4|
|   a|        1|   |      1|    3|   8|
|   a|        6|  8|      1|    1|   8|
+----+---------+---+-------+-----+----+

我试过了

val w = Window.partitionBy($"guid")

    val w1 = Window.partitionBy($"guid", $"timestamp")
    val w2 = Window.partitionBy($"guid").orderBy($"timestamp".desc).rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)

    val gg = df.toDS()
      .withColumn("minimum", min("timestamp").over(w))
      .withColumn("count", count("*").over(w1))
      .withColumn("agtM", coalesce(first($"agt", true).over(w2), lit("")))
      .dropDuplicates("guid", "timestamp")

agtM计算我不是很自信。我的目标是实现最小化改组,因为在这种情况下我们首先按guid分组,然后分组(guid,timestamp),逻辑上第二个分组应该在第一个创建的分区中进行。然后输出由guid分组并与另一个表连接。这两个数据都非常庞大(在TB中)所以想要以最小的改组实现这一点,并且不想在以后的mapGroups中移动计算(我可以简单地通过使用非空代理时间过滤组然后maxBy来完成agtM计算)时间戳)。能否请您建议实现上述目标的最佳途径?

修改

agtM计算已得到修复。为了给前面的操作提供更多的上下文,输出和另一个数据集的联合(一个额外的字段,我们在输出中将它保持为虚拟)将需要按键分组以产生最终结果。我还在考虑在每个分区(mapPartitions)中计算这些值(窗口w除外),然后将每个分区内的列表作为另一个列表进行计算并进行进一步计算。

3 个答案:

答案 0 :(得分:0)

要使用最后一个非空agtM值对agt进行回填,您可以last("agt", ignoreNulls)使用rowsBetween() w2

val ds = Seq(
  ("a", "1", ""),
  ("b", "2", "4"),
  ("a", "1", "3"),
  ("b", "2", "4"),
  ("c", "1", "3"),
  ("a", "6", "8"),
  ("b", "2", "4"),
  ("a", "1", "4")
).toDF("guid", "timestamp", "agt").
  as[(String, String, String)]

import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window

val w = Window.partitionBy($"guid")
val w1 = Window.partitionBy($"guid", $"timestamp")
val w2 = Window.partitionBy($"guid").orderBy($"timestamp".desc).
  rowsBetween(Window.unboundedPreceding, 0)

ds.
  withColumn("minimum", min("timestamp").over(w)).
  withColumn("count", count("*").over(w1)).
  withColumn("agt", when($"agt" =!= "", $"agt")).
  withColumn("agtM", last("agt", ignoreNulls = true).over(w2)).
  na.fill("", Seq("agt")).
  dropDuplicates("guid", "timestamp").
  show
// +----+---------+---+-------+-----+----+
// |guid|timestamp|agt|minimum|count|agtM|
// +----+---------+---+-------+-----+----+
// |   c|        1|  3|      1|    1|   3|
// |   b|        2|  4|      2|    3|   4|
// |   a|        1|   |      1|    3|   8|
// |   a|        6|  8|      1|    1|   8|
// +----+---------+---+-------+-----+----+

鉴于您的每个窗口规范ww1w2都有自己的特定要求,我不确定可以做多少来减少改组。您可以探索非窗口方法,尽管您打算创建的结果数据集似乎与使用窗口函数非常吻合。

答案 1 :(得分:0)

  
    

我需要计算
        按guid分组时每行的最小时间戳         按(guid,timestamp)分组时每个键的计数
        按guid分组并按时间戳(desc)排序然后取第一个非空agt的行的agtM“”

  

根据您的要求,您需要计算最小的时间戳,agt(最新)agt on guid组,并按guid和timestamp 分组。这些要求表明你需要三个分组和三个shuffling

第一次分组和改组 - 找到计数

val dfWithCount = df
      .groupBy("guid", "timestamp")
      .agg(count("guid").as("count"))

第二次和第三次分组和改组

最新的agt 即使用Window函数可以找到agtM,使用另一个groupBy和{{1}可以找到最小时间戳 }}

aggregation

最后,您val dfWithMinAndMax = df.withColumn("agtM", first("agt").over(windowSpec)) .groupBy("guid", "agtM") .agg(min("timestamp").as("minimum") ) 两个数据帧

join

这会为您提供正确的数据框 但没有agt

val finalDF = dfWithCount.join(dfWithMinAndMax, Seq("guid"))

我猜+----+---------+-----+----+-------+ |guid|timestamp|count|agtM|minimum| +----+---------+-----+----+-------+ |c |1 |1 |3 |1 | |b |2 |3 |4 |2 | |a |1 |3 |8 |1 | |a |6 |1 |8 |1 | +----+---------+-----+----+-------+ 并不重要,但如果你真的需要它,那么你需要另一个分组改组加入

agt

会给你

val dfWithAgt = df.groupBy("guid", "timestamp").agg(min("agt").as("agt"))

finalDF.join(dfWithAgt, Seq("guid", "timestamp"))

可以使用+----+---------+-----+----+-------+---+ |guid|timestamp|count|agtM|minimum|agt| +----+---------+-----+----+-------+---+ |c |1 |1 |3 |1 |3 | |b |2 |3 |4 |2 |4 | |a |1 |3 |8 |1 | | |a |6 |1 |8 |1 |8 | +----+---------+-----+----+-------+---+ 完成列顺序。

我希望答案很有帮助

答案 2 :(得分:0)

最初通过guid对其进行分区然后使用迭代器将在逻辑上做更少的改组。如果每个组内的数据都很大,则不确定效果。

df.toDS().groupByKey(_.guid).flatMapGroups((a,b) => {
          val list = b.toList
          val minimum = list.minBy(_.timestamp).timestamp
          val filteredList = list.filterNot(_.agt == "")
          val agtM = if(filteredList.isEmpty) "" else filteredList.maxBy(_.timestamp).agt
          list.groupBy(_.timestamp).map(r => (r._2.head.guid, r._1, r._2.head.agt, minimum, r._2.length, agtM))
        }).select($"_1".as("guid"), $"_2".as("timestamp"),
          $"_3".as("agt"), $"_4".as("minimum"), $"_5".as("count"), $"_6".as("agtM")).show()