如何在Java/Kotlin中创建返回复杂类型的Spark UDF?

如何在Java/Kotlin中创建返回复杂类型的Spark UDF?,java,apache-spark,kotlin,user-defined-functions,Java,Apache Spark,Kotlin,User Defined Functions,我正在尝试编写一个返回复杂类型的UDF: private val toPrice = UDF1<String, Map<String, String>> { s -> val elements = s.split(" ") mapOf("value" to elements[0], "currency" to elements[1]) } val type = DataTypes.createStructType(listOf(

我正在尝试编写一个返回复杂类型的UDF:

private val toPrice = UDF1<String, Map<String, String>> { s ->
    val elements = s.split(" ")
    mapOf("value" to elements[0], "currency" to elements[1])
}


val type = DataTypes.createStructType(listOf(
        DataTypes.createStructField("value", DataTypes.StringType, false),
        DataTypes.createStructField("currency", DataTypes.StringType, false)))
df.sqlContext().udf().register("toPrice", toPrice, type)
我得到一个神秘的错误:

原因:org.apache.spark.SparkException:无法执行用户定义的函数($anonfun$28:(字符串)=>struct)
位于org.apache.spark.sql.catalyst.expressions.GeneratedClass$GenerateEditorForCodeGenStage1.processNext(未知源)
位于org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)
位于org.apache.spark.sql.execution.whisttagecodegenexec$$anonfun$10$$anon$1.hasNext(whisttagecodegenexec.scala:614)
位于org.apache.spark.sql.execution.SparkPlan$$anonfun$2.apply(SparkPlan.scala:253)
位于org.apache.spark.sql.execution.SparkPlan$$anonfun$2.apply(SparkPlan.scala:247)
位于org.apache.spark.rdd.rdd$$anonfun$mapPartitionsInternal$1$$anonfun$apply$25.apply(rdd.scala:830)
位于org.apache.spark.rdd.rdd$$anonfun$mapPartitionsInternal$1$$anonfun$apply$25.apply(rdd.scala:830)
在org.apache.spark.rdd.MapPartitionsRDD.compute上(MapPartitionsRDD.scala:38)
在org.apache.spark.rdd.rdd.computeOrReadCheckpoint(rdd.scala:324)
位于org.apache.spark.rdd.rdd.iterator(rdd.scala:288)
在org.apache.spark.rdd.MapPartitionsRDD.compute上(MapPartitionsRDD.scala:38)
在org.apache.spark.rdd.rdd.computeOrReadCheckpoint(rdd.scala:324)
位于org.apache.spark.rdd.rdd.iterator(rdd.scala:288)
位于org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:87)
位于org.apache.spark.scheduler.Task.run(Task.scala:109)
位于org.apache.spark.executor.executor$TaskRunner.run(executor.scala:345)
位于java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
位于java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
运行(Thread.java:748)
原因:scala.MatchError:{value=138.0,currency=USD}(属于java.util.LinkedHashMap类)
在org.apache.spark.sql.catalyst.CatalystTypeConverters$StructConverter.toCatalystImpl(CatalystTypeConverters.scala:236)上
在org.apache.spark.sql.catalyst.CatalystTypeConverters$StructConverter.toCatalystImpl(CatalystTypeConverters.scala:231)上
位于org.apache.spark.sql.catalyst.CatalystTypeConverters$CatalystTypeConverter.toCatalyst(CatalystTypeConverters.scala:103)
在org.apache.spark.sql.CatalystTypeConverters$$anonfun$createToCatalystConverters$2.apply上(CatalystTypeConverters.scala:379)
... 还有19个
我尝试使用自定义数据类型:

class Price(val value: Double, val currency: String) : Serializable
使用返回该类型的UDF:

private val toPrice = UDF1<String, Price> { s ->
    val elements = s.split(" ")
    Price(elements[0].toDouble(), elements[1])
}
private val toPrice=UDF1{s->
val元素=s.split(“”)
价格(元素[0]。toDouble(),元素[1])
}
但是我得到了另一个
MatchError
,它抱怨
Price
类型

如何正确地编写可以返回复杂类型的UDF?

它很简单。转到并查找相应的类型

在Spark 2.3中

  • 如果将返回类型声明为
    StructType
    ,则函数必须返回
    org.apache.spark.sql.Row
  • 如果返回
    Map
    函数,则返回类型应为
    MapType
    ——显然不是您想要的

    • TL;DR该函数应该返回一个类为org.apache.spark.sql.Row的对象

      Spark提供了两种主要的
      UDF
      定义变体

    • udf
      使用Scala反射的变体:

      • def-udf[RT](f:()⇒ RT)(隐式arg0:TypeTag[RT]):UserDefinedFunction
      • def-udf[RT,A1](f:(A1)⇒ RT)(隐式arg0:TypeTag[RT],arg1:TypeTag[A1]):UserDefinedFunction
      • def-udf[RT,A1,A2,…,A10](f:(A1,A2,…,A10)⇒ (隐式arg0:TypeTag[RT],arg1:TypeTag[A1],arg2:TypeTag[A2],…,arg10:TypeTag[A10])
      哪个定义

      Scala的闭包。。。参数作为用户定义函数(UDF)。根据Scala闭包的签名自动推断数据类型

      这些变体在没有原子模式或代数数据类型的情况下使用。例如,所讨论的函数将在Scala中定义:

      case类价格(值:双精度,币种:字符串)
      val df=序号(“1美元”)。toDF(“价格”)
      val toPrice=udf((s:String)=>scala.util.Try{
      s拆分(“”)匹配{
      案例数组(价格,货币)=>price(price.toDouble,货币)
      }
      }.toOption)
      df.select(toPrice($“价格”)).show
      // +----------+
      //|自定义项(价格)|
      // +----------+
      //|[1.0美元]|
      // +----------+
      
      在这个变量中,返回类型是自动编码的

      由于它依赖于反射,此变体主要针对Scala用户

    • udf
      提供模式定义的变体(此处使用的变体)。此变量的返回类型应与
      Dataset[Row]
      的返回类型相同:

      • 正如在另一个答案中指出的,您只能使用中列出的类型(装箱或未装箱的原子类型,
        java.sql.Timestamp
        /
        java.sql.Date
        ,以及高级集合)

      • 复杂结构(
        structs
        /
        StructTypes
        )使用
        org.apache.spark.sql.Row
        表示。不允许与代数数据类型或等效数据类型混合。例如(Scala代码)

        不是

        或者任何混合变体,比如

      提供此变体主要是为了确保Java互操作性

      在这种情况下(相当于所讨论的定义),定义应类似于以下定义:

      import org.apache.spark.sql.types_
      导入org.apache.spark.sql.functions.udf
      导入org.apache.spark.sql.Row
      val schema=StructType(Seq(
      StructField(“值”,双重类型,false),
      StructField(“货币”,StringType,false)
      ))
      val toPrice=udf((s:String)=>scala.util.Try{
      s拆分(“”)匹配{
      案例数组(价格、货币)
      
      private val toPrice = UDF1<String, Price> { s ->
          val elements = s.split(" ")
          Price(elements[0].toDouble(), elements[1])
      }
      
      struct<_1:int,_2:struct<_1:string,_2:struct<_1:double,_2:int>>>
      
      Row(1, Row("foo", Row(-1.0, 42))))
      
      (1, ("foo", (-1.0, 42))))
      
      Row(1, Row("foo", (-1.0, 42))))
      
      def createDataFrame(rows: List[Row], schema: StructType): DataFrame 
      
      def createDataFrame[A <: Product](data: Seq[A])(implicit arg0: TypeTag[A]): DataFrame 
      
      import org.apache.spark.sql.functions._
      
      df.withColumn("price", struct(
        split($"price", " ")(0).cast("double").alias("price"),
        split($"price", " ")(1).alias("currency")
      ))