Spark - 递归函数作为udf生成异常

时间:2017-09-27 09:01:14

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

我正在使用DataFrames,哪些元素的架构类似于:

root
 |-- NPAData: struct (nullable = true)
 |    |-- NPADetails: struct (nullable = true)
 |    |    |-- location: string (nullable = true)
 |    |    |-- manager: string (nullable = true)
 |    |-- service: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- serviceName: string (nullable = true)
 |    |    |    |-- serviceCode: string (nullable = true) 
 |-- NPAHeader: struct (nullable = true)
 |    |    |-- npaNumber: string (nullable = true)
 |    |    |-- date: string (nullable = true)

在我的DataFrame中,我想对具有相同NPAHeader.code的所有元素进行分组,所以为此我使用以下行:

val groupedNpa = orderedNpa.groupBy($"NPAHeader.code" ).agg(collect_list(struct($"NPAData",$"NPAHeader")).as("npa"))

在此之后,我有一个包含以下架构的数据框:

StructType(StructField(npaNumber,StringType,true), StructField(npa,ArrayType(StructType(StructField(NPAData...)))))

每一行的一个例子类似于:

[1234,WrappedArray([npaNew,npaOlder,...npaOldest])]

现在我想要的是生成另一个DataFrame,只拾取WrappedArray中的一个元素,所以我想要一个类似于的输出:

[1234,npaNew]

注意:WrappedArray中选择的元素是在迭代整个WrappedArray之后匹配Complext逻辑的元素。但是为了简化这个问题,我将总是拿起WrappedArray的最后一个元素(迭代后全部)。

为此,我想定义一个递归udf

import org.apache.spark.sql.functions.udf

def returnRow(elementList : Row)(index:Int): Row = {
  val dif = elementList.size - index
  val row :Row = dif match{
    case 0 => elementList.getAs[Row](index)
    case _ => returnRow(elementList)(index + 1) 
  }
  row
} 

val returnRow_udf = udf(returnRow _)


groupedNpa.map{row => (row.getAs[String]("npaNumber"),returnRow_udf(groupedNpa("npa")(0)))}

但我在地图中收到以下错误:

  

线程中的异常" main" java.lang.UnsupportedOperationException:   类型Int =>的模式单位不受支持

我做错了什么?

顺便说一句,我不确定我是否正确传递npagroupedNpa("npa")。我正在访问WrappedArray作为一行,因为我不知道如何迭代Array[Row](数组[行]中不存在get(index)方法)

1 个答案:

答案 0 :(得分:1)

TL; DR 只需使用How to select the first row of each group?

中描述的方法之一

如果要使用复杂逻辑,并返回Row,可以跳过SQL API并使用groupByKey

val f: (String, Iterator[org.apache.spark.sql.Row]) => Row
val encoder: Encoder 
df.groupByKey(_.getAs[String]("NPAHeader.code")).mapGroups(f)(encoder)

或更好:

val g: (Row, Row) => Row

df.groupByKey(_.getAs[String]("NPAHeader.code")).reduceGroups(g)

其中encoder是有效的RowEncoderEncoder error while trying to map dataframe row to updated row)。

您的代码有多种错误:

  • groupBy不保证值的顺序。所以:

    orderBy(...).groupBy(....).agg(collect_list(...))
    

    可以具有非确定性输出。如果你真的决定走这条路,你应该跳过orderBy并明确地对收集的数组进行排序。

  • 您无法将curried函数传递给udf。你必须首先取消它,但它需要不同的参数顺序(见下面的例子)。

  • 如果可以的话,这可能是调用它的正确方法(注意你省略了第二个参数):

    returnRow_udf(groupedNpa("npa")(0))
    

    更糟糕的是,您可以在map内拨打电话,udfs根本不适用。

  • udf无法返回Row。它必须返回external Scala type

  • array<struct>的外部代表是Seq[Row]。您不能只用Row替换它。
  • 可以使用apply索引访问SQL数组:

    df.select($"array"(size($"array") - 1))
    

    但由于非确定性,这不是一种正确的方法。您可以申请sort_array,但正如开头所指出的那样,有更有效的解决方案。

  • 令人惊讶的递归并不那么重要。你可以设计这样的功能:

    def size(i: Int=0)(xs: Seq[Any]): Int = xs match {
      case Seq() => i
      case null => i
      case Seq(h, t @ _*) => size(i + 1)(t)
    }
    
    val size_ = udf(size() _)
    

    它可以正常工作:

    Seq((1, Seq("a", "b", "c"))).toDF("id", "array")
      .select(size_($"array"))
    

    虽然递归是一种矫枉过正,如果你可以迭代Seq