Scala JSON对象的DataFrame分解列表

Scala JSON对象的DataFrame分解列表,scala,apache-spark,apache-spark-sql,distributed-computing,Scala,Apache Spark,Apache Spark Sql,Distributed Computing,我有以下格式的JSON数据: { "date": 100 "userId": 1 "data": [ { "timeStamp": 101, "reading": 1 }, { "timeStamp": 102, "reading": 2 } ] } { "date":

我有以下格式的JSON数据:

{
     "date": 100
     "userId": 1
     "data": [
         {
             "timeStamp": 101,
             "reading": 1
         },
         {
             "timeStamp": 102,
             "reading": 2
         }
     ]
 }
 {
     "date": 200
     "userId": 1
     "data": [
         {
             "timeStamp": 201,
             "reading": 3
         },
         {
             "timeStamp": 202,
             "reading": 4
         }
     ]
 }
我将其读入Spark SQL:

val df = SQLContext.read.json(...)
df.printSchema
// root
//  |-- date: double (nullable = true)
//  |-- userId: long (nullable = true)
//  |-- data: array (nullable = true)
//  |     |-- element: struct (containsNull = true)
//  |     |    |-- timeStamp: double (nullable = true)
//  |     |    |-- reading: double (nullable = true)
我想将其转换为每次读取一行。据我所知,每一次转换都应该产生一个新的数据帧,因此以下各项应该可以工作:

import org.apache.spark.sql.functions.explode
val exploded = df
    .withColumn("reading", explode(df("data.reading")))
    .withColumn("timeStamp", explode(df("data.timeStamp")))
    .drop("data")
exploded.printSchema
// root
//  |-- date: double (nullable = true)
//  |-- userId: long (nullable = true)
//  |-- timeStamp: double (nullable = true)
//  |-- reading: double (nullable = true)
结果架构是正确的,但我得到每个值两次:

exploded.show
// +-----------+-----------+-----------+-----------+
// |       date|     userId|  timeStamp|    reading|
// +-----------+-----------+-----------+-----------+
// |        100|          1|        101|          1|
// |        100|          1|        101|          1|
// |        100|          1|        102|          2|
// |        100|          1|        102|          2|
// |        200|          1|        201|          3|
// |        200|          1|        201|          3|
// |        200|          1|        202|          4|
// |        200|          1|        202|          4|
// +-----------+-----------+-----------+-----------+
我的感觉是,对两次爆炸的懒惰评估有些东西我不理解

有没有办法让上述代码正常工作?或者我应该一起使用不同的方法

结果的模式是正确的,但是我得到每个值两次

虽然模式是正确的,但您提供的输出并不反映实际结果。实际上,您将得到每个输入行的
时间戳
读取
的笛卡尔乘积

我的感觉是,懒惰的评估有些道理

不,它与惰性评估无关。使用
explode
的方法是错误的。为了了解发生了什么,让我们跟踪
date
equal 100的执行情况:

val df100 = df.where($"date" === 100)
一步一步。首先,分解将生成两行,一行用于1,一行用于2:

val df100WithReading = df100.withColumn("reading", explode(df("data.reading")))

df100WithReading.show
// +------------------+----+------+-------+
// |              data|date|userId|reading|
// +------------------+----+------+-------+
// |[[1,101], [2,102]]| 100|     1|      1|
// |[[1,101], [2,102]]| 100|     1|      2|
// +------------------+----+------+-------+
第二次分解为上一步中的每一行生成两行(
timeStamp
equal 101和102):

val df100WithReadingAndTs = df100WithReading
  .withColumn("timeStamp", explode(df("data.timeStamp")))

df100WithReadingAndTs.show
// +------------------+----+------+-------+---------+
// |              data|date|userId|reading|timeStamp|
// +------------------+----+------+-------+---------+
// |[[1,101], [2,102]]| 100|     1|      1|      101|
// |[[1,101], [2,102]]| 100|     1|      1|      102|
// |[[1,101], [2,102]]| 100|     1|      2|      101|
// |[[1,101], [2,102]]| 100|     1|      2|      102|
// +------------------+----+------+-------+---------+
如果要获得正确的结果,请分解数据,然后选择:

val exploded = df.withColumn("data", explode($"data"))
  .select($"userId", $"date",
    $"data".getItem("reading"),  $"data".getItem("timestamp"))

exploded.show
// +------+----+-------------+---------------+
// |userId|date|data[reading]|data[timestamp]|
// +------+----+-------------+---------------+
// |     1| 100|            1|            101|
// |     1| 100|            2|            102|
// |     1| 200|            3|            201|
// |     1| 200|            4|            202|
// +------+----+-------------+---------------+

内部字段选择的缩写形式也适用:
df.withColumn(“数据”,分解($“数据”))。选择($“数据.读取”、$“数据.时间戳”、$“日期”、$“用户ID”)。foreach(println)