and*_*ove 5 user-defined-types apache-spark apache-spark-sql
我正在尝试实现自定义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)
})
}
}
Run Code Online (Sandbox Code Playgroud)
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"
}
Run Code Online (Sandbox Code Playgroud)
如果我只是SELECT person FROM person
那么查询工作。即使在架构中定义了属性,我也无法引用SQL中的属性。
您会收到此错误,因为由 定义的架构sqlType
永远不会公开,并且不适合直接访问。它只是提供了一种使用本机 Spark SQL 类型来表达复杂数据类型的方法。
您可以使用 UDF 访问各个属性,但首先让我们看看内部结构确实没有公开:
dataFrame.printSchema
// root
// |-- person_id: integer (nullable = true)
// |-- person: mockperso (nullable = true)
Run Code Online (Sandbox Code Playgroud)
要创建 UDF,我们需要将给定 UDT 表示的类型的对象作为参数的函数:
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
Run Code Online (Sandbox Code Playgroud)
可以使用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|
// +----------+---------+---+
Run Code Online (Sandbox Code Playgroud)
要将这些与原始 SQL 一起使用,您可以通过以下方式注册函数SQLContext
:
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|
// +----------+---------+
Run Code Online (Sandbox Code Playgroud)
不幸的是它带有价格标签。首先,每个操作都需要反序列化。它还极大地限制了查询优化的方式。特别是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|
// +----------+---------+
Run Code Online (Sandbox Code Playgroud)
并为实际类型扩展(例如内置)或可以从特定表示(例如枚举)VectorUDT
中受益的事物保留 UDT 。
归档时间: |
|
查看次数: |
1671 次 |
最近记录: |