Spark SQL引用UDT的属性

时间:2015-11-17 01:53:09

标签: apache-spark apache-spark-sql user-defined-types

我正在尝试实现自定义UDT并能够从Spark SQL引用它(如Spark SQL白皮书第4.4.2节中所述)。

真正的例子是使用Cap'n Proto或类似的方法使用堆外数据结构支持自定义UDT。

对于这篇文章,我编写了一个人为的例子。我知道我可以使用Scala案例类而不必完成任何工作,但这不是我的目标。

例如,我有Person包含多个属性,我希望能够SELECT person.first_name FROM person。我遇到了错误Can't extract value from person#1,我不确定原因。

以下是完整来源(也可在https://github.com/andygrove/spark-sql-udt获得)

package com.theotherandygrove

import org.apache.spark.sql.types._
import org.apache.spark.sql.{Row, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}

object Example {

  def main(arg: Array[String]): Unit = {

    val conf = new SparkConf()
      .setAppName("Example")
      .setMaster("local[*]")

    val sc = new SparkContext(conf)

    val sqlContext = new SQLContext(sc)

    val schema = StructType(List(
      StructField("person_id", DataTypes.IntegerType, true),
      StructField("person", new MockPersonUDT, true)))

    // load initial RDD
    val rdd = sc.parallelize(List(
      MockPersonImpl(1),
      MockPersonImpl(2)
    ))

    // convert to RDD[Row]
    val rowRdd = rdd.map(person => Row(person.getAge, person))

    // convert to DataFrame (RDD + Schema)
    val dataFrame = sqlContext.createDataFrame(rowRdd, schema)

    // register as a table
    dataFrame.registerTempTable("person")

    // selecting the whole object works fine
    val results = sqlContext.sql("SELECT person.first_name FROM person WHERE person.age < 100")

    val people = results.collect

    people.map(row => {
      println(row)
    })

  }

}
trait MockPerson {
  def getFirstName: String
  def getLastName: String
  def getAge: Integer
  def getState: String
}

class MockPersonUDT extends UserDefinedType[MockPerson] {

  override def sqlType: DataType = StructType(List(
    StructField("firstName", StringType, nullable=false),
    StructField("lastName", StringType, nullable=false),
    StructField("age", IntegerType, nullable=false),
    StructField("state", StringType, nullable=false)
  ))

  override def userClass: Class[MockPerson] = classOf[MockPerson]

  override def serialize(obj: Any): Any = obj.asInstanceOf[MockPersonImpl].getAge

  override def deserialize(datum: Any): MockPerson = MockPersonImpl(datum.asInstanceOf[Integer])
}

@SQLUserDefinedType(udt = classOf[MockPersonUDT])
@SerialVersionUID(123L)
case class MockPersonImpl(n: Integer) extends MockPerson with Serializable {
  def getFirstName = "First" + n
  def getLastName = "Last" + n
  def getAge = n
  def getState = "AK"
}

如果我只是SELECT person FROM person,那么查询就可以了。我只是不能在SQL中引用属性,即使它们是在模式中定义的。

1 个答案:

答案 0 :(得分:3)

您会收到此错误,因为dataFrame.printSchema // root // |-- person_id: integer (nullable = true) // |-- person: mockperso (nullable = true) 定义的架构永远不会公开,也不会直接访问。它只是提供了一种使用本机Spark SQL类型表达复杂数据类型的方法。

您可以使用UDF访问各个属性,但首先要显示内部结构确实未公开:

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

val getFirstName = (person: MockPerson) => person.getFirstName
val getLastName = (person: MockPerson) => person.getLastName
val getAge = (person: MockPerson) => person.getAge

要创建UDF,我们需要一些函数,这些函数将给定UDT表示的类型的对象作为参数:

udf

可以使用val getFirstNameUDF = udf(getFirstName) val getLastNameUDF = udf(getLastName) val getAgeUDF = udf(getAge) dataFrame.select( getFirstNameUDF($"person").alias("first_name"), getLastNameUDF($"person").alias("last_name"), getAgeUDF($"person").alias("age") ).show() // +----------+---------+---+ // |first_name|last_name|age| // +----------+---------+---+ // | First1| Last1| 1| // | First2| Last2| 2| // +----------+---------+---+ 函数包装:

SQLContext

要在原始SQL中使用这些,您可以通过sqlContext.udf.register("first_name", getFirstName) sqlContext.udf.register("last_name", getLastName) sqlContext.udf.register("age", getAge) sqlContext.sql(""" SELECT first_name(person) AS first_name, last_name(person) AS last_name FROM person WHERE age(person) < 100""").show // +----------+---------+ // |first_name|last_name| // +----------+---------+ // | First1| Last1| // | First2| Last2| // +----------+---------+ 注册函数:

join

不幸的是它附带了价格标签。首先,每个操作都需要反序列化。它还大大限制了查询优化的方式。特别是对其中一个字段的任何StructType操作都需要笛卡尔积。

实际上,如果要编码复杂结构(包含可以使用内置类型表示的属性),最好使用case class Person(first_name: String, last_name: String, age: Int) val df = sc.parallelize( (1 to 2).map(i => (i, Person(s"First$i", s"Last$i", i)))).toDF("id", "person") df.printSchema // root // |-- id: integer (nullable = false) // |-- person: struct (nullable = true) // | |-- first_name: string (nullable = true) // | |-- last_name: string (nullable = true) // | |-- age: integer (nullable = false) df .where($"person.age" < 100) .select($"person.first_name", $"person.last_name") .show // +----------+---------+ // |first_name|last_name| // +----------+---------+ // | First1| Last1| // | First2| Last2| // +----------+---------+

VectorUDT

并为内置div { height: 616px; background:url(http://i.stack.imgur.com/eXA9b.jpg) } 等实际类型扩展或可从特定表示like enumerations中受益的内容保留UDT。