Apache spark 条件聚合火花数据帧

Apache spark 条件聚合火花数据帧,apache-spark,apache-spark-sql,Apache Spark,Apache Spark Sql,我想了解在这种情况下在Spark中进行聚合的最佳方法: import sqlContext.implicits._ import org.apache.spark.sql.functions._ case class Person(name:String, acc:Int, logDate:String) val dateFormat = "dd/MM/yyyy" val filterType = // Could has "MIN" or "MAX" depending on a run

我想了解在这种情况下在Spark中进行聚合的最佳方法:

import sqlContext.implicits._  
import org.apache.spark.sql.functions._
case class Person(name:String, acc:Int, logDate:String)
val dateFormat = "dd/MM/yyyy"
val filterType = // Could has "MIN" or "MAX" depending on a run parameter
val filterDate = new Timestamp(System.currentTimeMillis)

val df = sc.parallelize(List(Person("Giorgio",20,"31/12/9999"),
                             Person("Giorgio",30,"12/10/2009")
                             Person("Diego",  10,"12/10/2010"),
                             Person("Diego",  20,"12/10/2010"),
                             Person("Diego",  30,"22/11/2011"), 
                             Person("Giorgio",10,"31/12/9999"),
                             Person("Giorgio",30,"31/12/9999"))).toDF()

val df2 = df.withColumn("logDate",unix_timestamp($"logDate",dateFormat).cast(TimestampType))

val df3 = df.groupBy("name").agg(/*conditional aggregation*/)
df3.show /*Expected output show  below */
基本上,我想按
name
列对所有记录进行分组,然后根据
filterType
参数,我想过滤一个人的所有有效记录,然后在过滤后,我想对所有
acc
值求和,得到最终结果
DataFrame
带有nametotalAcc

例如:

  • filterType=MIN,我想获取具有MIN(logDate)的所有记录,可能有很多记录,因此基本上在本例中我完全忽略filterDate参数:
2010年10月10日,迭戈
迭戈,20,12/10/2010
乔治,30,12/10/2009

聚合的最终结果是:(迭戈,30),(乔治,30)

  • filterType=MAX,我想用logDate>filterDate获取所有记录,对于一个键,我没有任何与此条件相关的记录,我需要像在min场景中那样使用min(logDate)获取记录,因此:
2010年10月10日至12日,迭戈
迭戈,20,12/10/2010
乔治,20,31/12/9999
乔治,1999年12月10日,31日
乔治,30,31/12/9999

聚合的最终结果是:(迭戈,30岁),(乔治,60岁)
在这种情况下,对于Diego,我没有任何带有logDate>logFilter的记录,因此我回退应用MIN场景,只为Diego获取带有MIN logDate的所有记录。

您不需要条件聚合。只需过滤:

df
  .where(if (filterType == "MAX") $"logDate" < filterDate else $"logDate" > filterDate)
  .groupBy("name").agg(sum($"acc")
df
。其中(如果(filterType==“MAX”)$“logDate”filterDate)
.groupBy(“名称”).agg(金额($“acc”)

您可以使用
when/other
作为

df2.groupBy("name").agg(sum(when(lit(filterType) === "MIN" && $"logDate" < filterDate, $"acc").otherwise(when(lit(filterType) === "MAX" && $"logDate" > filterDate, $"acc"))).as("sum"))
    .filter($"sum".isNotNull)
哪个输出为

+-------+--------+
|name   |sum(acc)|
+-------+--------+
|Diego  |10      |
|Giorgio|60      |
+-------+--------+
更新 由于问题是随着逻辑的改变而更新的,下面是对改变后的场景的想法和解决方案

import org.apache.spark.sql.expressions._
def windowSpec = Window.partitionBy("name").orderBy($"logDate".asc)

val minDF = df2.withColumn("minLogDate", first("logDate").over(windowSpec)).filter($"minLogDate" === $"logDate")
  .groupBy("name")
  .agg(sum($"acc").as("sum"))

val finalDF =
  if(filterType == "MIN") {
    minDF
  }
  else if(filterType == "MAX"){
    val tempMaxDF = df2
      .groupBy("name")
      .agg(sum(when($"logDate" > filterDate,$"acc")).as("sum"))

    tempMaxDF.filter($"sum".isNull).drop("sum").join(minDF, Seq("name"), "left").union(tempMaxDF.filter($"sum".isNotNull))
  }
  else {
    df2
  }
所以对于
filterType=MIN
您应该

+-------+---+
|name   |sum|
+-------+---+
|Diego  |30 |
|Giorgio|30 |
+-------+---+
对于
filterType=MAX
,您应该

+-------+---+
|name   |sum|
+-------+---+
|Diego  |30 |
|Giorgio|60 |
+-------+---+
如果
filterType
不是
MAX
MIN
,则返回原始数据帧


我希望答案是有帮助的

如果你有两个Diego的记录都小于filterDate怎么办?你能澄清一下Diego的输出是什么吗?如果我只有小于logDate的记录,对于flowType MIN,我将使用MIN logDate记录,如果两者都有MIN logDate,我将求和。对于MAX场景,如果Diego,我没有使用logDate>logfilter的任何记录,然后我将查找使用min logDate的记录,与使用min scenarioI时完全相同。对于您之前提到的场景,我也将再次更新。Diego的2011年记录必须从这两种场景中排除,因为对于min,它不使用min logDate;对于MAX场景,它失败logDate>logFilterI,我已更新了我的an你为什么选择使用窗口函数来实现这个解决方案?它给你带来了某种性能提升还是其他什么?这主要是因为需要在一个组内进行计算并输出所有行,而窗口函数最适合于此。你还有什么其他想法吗?@GiorgioI我对数据帧非常熟悉,maybe您可以帮助我澄清以下几点:我考虑的是性能,因为实际上在生产中,我将面临3000-3500万条记录的数据帧,我会问自己,在MAX filterType的情况下,窗口和连接是否会成为瓶颈。您的要求表明您需要确定最小值这就是为什么一个窗口函数和你的第二个最大值要求表明你需要一个最小值和最大值的连接。你总是可以搜索最佳的替代方案。但我提出的解决方案是我所知道的最好的解决方案。如果你找到比这个更好的选择,请通过回答让我知道。如果不是,欢迎你加入upv注意并接受答案。thanksI并不是什么争论。我只是想和你分享我的疑问,因为你似乎非常专家。我只是问自己,对于你来说,在性能方面,是否最好在一个数据帧中完成所有计算(如果可能,可能使用你的第一个解决方案,并在新场景中进行调整)或者加入更多的数据框。谢谢
+-------+---+
|name   |sum|
+-------+---+
|Diego  |30 |
|Giorgio|60 |
+-------+---+