如何将镶木地板数据转换为带有spark的案例类?

时间:2015-12-03 16:25:50

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

我已经有大量的案例类,我已经在spark中将数据保存为镶木地板,例如:

case class Person(userId: String,
              technographic: Option[Technographic] = None,
              geographic: Option[Geographic] = None)

case class Technographic(browsers: Seq[Browser], 
                     devices: Seq[Device],
                     oss: Seq[Os])

case class Browser(family: String,
               major: Option[String] = None, 
               language: String

...

如何将磁盘上的数据转换回这些案例类?

我需要能够选择多个列并对其进行分解,以便每个列表(例如browsers)的所有子列表具有相同的长度。

E.g。鉴于此原始数据:

Person(userId="1234",
  technographic=Some(Technographic(browsers=Seq(
    Browser(family=Some("IE"), major=Some(7), language=Some("en")),
    Browser(family=None, major=None, language=Some("en-us")),
    Browser(family=Some("Firefox), major=None, language=None)
  )),
  geographic=Some(Geographic(...))
)

我需要,例如浏览器数据如下(以及能够选择所有列):

family=IE, major=7, language=en
family=None, major=None, language=en-us
family=Firefox, major=None, language=None
如果spark可以explode每个列表项,我可以获得

。目前它只会做类似的事情(并且无论如何explode不会使用多列):

browsers.family = ["IE", "Firefox"]
browsers.major = [7]
browsers.language = ["en", "en-us"]

那么如何使用spark 1.5.2从所有这些嵌套的可选数据中重建用户记录(产生一行数据的整个案例类)呢?

一种可能的方法是:

val df = sqlContext.read.parquet(inputPath)
df.registerTempTable("person")
val fields = df.select("desc person")
df.select("select * from person").map { x => 
  ... // somehow zip `fields` with the values so that I can 
      // access values by column name instead of index 
      // (which is brittle), but how?
}

2 个答案:

答案 0 :(得分:4)

鉴于

case class Browser(family: String,
                   major: Option[Int] = None,
                   language: String)

case class Tech(browsers: Seq[Browser],
                devices: Seq[String],
                oss: Seq[String])


case class Person(userId: String,
                  tech: Option[Tech] = None,
                  geographic: Option[String] = None)

以及org.apache.spark.sql.Row

的一些便利类型/功能
type A[E] = collection.mutable.WrappedArray[E]

implicit class RichRow(val r: Row) {
  def getOpt[T](n: String): Option[T] = {
    if (isNullAt(n)) {
      None
    } else {
      Some(r.getAs[T](n))
    }
  }

  def getStringOpt(n: String) = getOpt[String](n)
  def getString(n: String) = getStringOpt(n).get

  def getIntOpt(n: String) = getOpt[Int](n)
  def getInt(n: String) = r.getIntOpt(n).get

  def getArray[T](n: String) = r.getAs[A[T]](n)

  def getRow(n: String) = r.getAs[Row](n)
  def getRows(n: String) = r.getAs[A[Row]](n)

  def isNullAt(n: String) = r.isNullAt(r.fieldIndex(n))
}

然后可以在某些函数中组织解析:

def toBrowser(r: Row): Browser = {
  Browser(
    r.getString("family"),
    r.getIntOpt("major"),
    r.getString("language"))
}

def toBrowsers(rows: A[Row]): Seq[Browser] = {
  rows.map(toBrowser)
}

def toTech(r: Row): Tech = {
  Tech(
    toBrowsers(r.getRows("browsers")),
    r.getArray[String]("devices"),
    r.getArray[String]("oss"))
}

def toTechOpt(r: Row): Option[Tech] = {
  Option(r).map(toTech)
}

def toPerson(r: Row): Person = {
  Person(
    r.getString("userId"),
    toTechOpt(r.getRow("tech")),
    r.getStringOpt("geographic"))
}

所以你可以写

df.map(toPerson).collect().foreach(println)
  • 我已将解析功能组织成"独立"方法。我通常将这些作为apply放入案例类的伴随对象中,或者作为Row的隐式值类。功能的原因是这更容易粘贴到spark-shell

  • 每个解析函数直接处理普通列和数组,但在遇到集合时委托给另一个函数(SeqOption - 这些代表下一个嵌套级别)

  • implict class应该extend AnyVal,但这又不能粘贴到spark-shell

答案 1 :(得分:1)

要详细说明接受的答案,它并不能正确处理空值。您需要尝试将其强制转换为字符串,以确定它是否为空。但是,只有在值为null时才会成功 - 如果该值为非null,则会导致转换异常。

困惑?这是代码:

implicit class RichRow(val r: Row) extends AnyVal {

    def getBoolean(n: String) = r.getAs[Boolean](n)
    def getBooleanOpt(n: String) = Try(r.getString(n)) match {
      case Success(_) => None
      case _ => Option(r.getBoolean(n))
    }

    def getString(n: String) = r.getAs[String](n)
    def getStringOpt(n: String) = Option(r.getString(n))

    def getLong(n: String) = r.getAs[Long](n)
    def getLongOpt(n: String) = Try(r.getString(n)) match {
      case Success(_) => None
      case _ => Option(r.getLong(n))
    } 

    def getInt(n: String) = r.getAs[Int](n)
    def getIntOpt(n: String) = Try(r.getString(n)) match {
      case Success(_) => None
      case _ => Option(r.getInt(n))
    } 

    def getFloat(n: String) = r.getAs[Float](n)
    def getFloatOpt(n: String) = Try(r.getString(n)) match {
      case Success(_) => None
      case _ => Option(r.getFloat(n))
    }

    def getArray[T](n: String) = r.getAs[A[T]](n)

    def getRow(n: String) = r.getAs[Row](n)
    def getRows(n: String): A[Row] = r.getAs[A[Row]](n)
  }
}