【问题标题】:Spark AnalysisException when "flattening" DataFrame in Spark SQL在 Spark SQL 中“展平”数据帧时出现 Spark AnalysisException
【发布时间】:2019-09-10 00:48:36
【问题描述】:

我正在使用here 给出的方法在 Spark SQL 中展平 DataFrame。这是我的代码:

package com.acme.etl.xml

import org.apache.spark.sql.types._ 
import org.apache.spark.sql.{Column, SparkSession}

object RuntimeError {   def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName("FlattenSchema").getOrCreate()
    val rowTag = "idocData"
    val dataFrameReader =
        spark.read
          .option("rowTag", rowTag)
    val xmlUri = "bad_011_1.xml"
    val df =
        dataFrameReader
          .format("xml")
          .load(xmlUri)
    val schema: StructType = df.schema
    val columns: Array[Column] = flattenSchema(schema)
    val df2 = df.select(columns: _*)

  }

  def flattenSchema(schema: StructType, prefix: String = null) : Array[Column] = {
    schema.fields.flatMap(f => {
      val colName: String = if (prefix == null) f.name else prefix + "." + f.name
      val dataType = f.dataType
      dataType match {
        case st: StructType => flattenSchema(st, colName)
        case _: StringType => Array(new org.apache.spark.sql.Column(colName))
        case _: LongType => Array(new org.apache.spark.sql.Column(colName))
        case _: DoubleType => Array(new org.apache.spark.sql.Column(colName))
        case arrayType: ArrayType => arrayType.elementType match {
          case structType: StructType => flattenSchema(structType, colName)
        }
        case _ => Array(new org.apache.spark.sql.Column(colName))
      }
    })
  }
}

很多时候,这工作正常。但是对于下面给出的 XML:

<Receive xmlns="http://Microsoft.LobServices.Sap/2007/03/Idoc/3/ORDERS05/ZORDERS5/702/Receive">
    <idocData>
        <E2EDP01008GRP xmlns="http://Microsoft.LobServices.Sap/2007/03/Types/Idoc/3/ORDERS05/ZORDERS5/702">
            <E2EDPT1001GRP>
                <E2EDPT2001>
                    <DATAHEADERCOLUMN_DOCNUM>0000000141036013</DATAHEADERCOLUMN_DOCNUM>
                </E2EDPT2001>
                <E2EDPT2001>
                    <DATAHEADERCOLUMN_DOCNUM>0000000141036013</DATAHEADERCOLUMN_DOCNUM>
                </E2EDPT2001>
            </E2EDPT1001GRP>
        </E2EDP01008GRP>
        <E2EDP01008GRP xmlns="http://Microsoft.LobServices.Sap/2007/03/Types/Idoc/3/ORDERS05/ZORDERS5/702">
        </E2EDP01008GRP>
    </idocData>
</Receive>

出现此异常:

Exception in thread "main" org.apache.spark.sql.AnalysisException: cannot resolve '`E2EDP01008GRP`.`E2EDPT1001GRP`.`E2EDPT2001`['DATAHEADERCOLUMN_DOCNUM']' due to data type mismatch: argument 2 requires integral type, however, ''DATAHEADERCOLUMN_DOCNUM'' is of string type.;;
'Project [E2EDP01008GRP#0.E2EDPT1001GRP.E2EDPT2001[DATAHEADERCOLUMN_DOCNUM] AS DATAHEADERCOLUMN_DOCNUM#3, E2EDP01008GRP#0._VALUE AS _VALUE#4, E2EDP01008GRP#0._xmlns AS _xmlns#5]
+- Relation[E2EDP01008GRP#0] XmlRelation(<function0>,Some(/Users/paulreiners/s3/cdi-events-partition-staging/content_acme_purchase_order_json_v1/bad_011_1.xml),Map(rowtag -> idocData, path -> /Users/paulreiners/s3/cdi-events-partition-staging/content_acme_purchase_order_json_v1/bad_011_1.xml),null)

这是什么原因造成的?

【问题讨论】:

  • 您能否提供您正在使用的确切代码。也是该程序适用的一个示例 XML 文件。异常消息中也清楚地提到了失败的原因。
  • 我添加了确切的代码。它失败的示例 XML 文件的完整内容在原始发布中并保留在那里。如果异常消息对我来说很清楚,我就不会问这个问题。

标签: apache-spark apache-spark-sql


【解决方案1】:

您的文档包含一个多值数组,因此您无法一次性将其完全展平,因为您无法为数组的两个元素赋予相同的列名。 此外,在列名中使用点通常不是一个好主意,因为它很容易混淆 Spark 解析器,并且需要始终进行转义。

扁平化此类数据集的常用方法是为数组的每个元素创建新行。 您可以使用 explode 函数来执行此操作,但您需要递归调用您的 flatten 操作,因为 explode 不能嵌套。

以下代码按预期工作,使用 '_' 而不是 '.'作为列名分隔符:

import org.apache.spark.sql.types._ 
import org.apache.spark.sql.{Column, SparkSession}
import org.apache.spark.sql.{Dataset, Row}

object RuntimeError {   

  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName("FlattenSchema").getOrCreate()
    val rowTag = "idocData"
    val dataFrameReader = spark.read.option("rowTag", rowTag)
    val xmlUri = "bad_011_1.xml"
    val df = dataFrameReader.format("xml").load(xmlUri)

    val df2 = flatten(df)

  }

  def flatten(df: Dataset[Row], prefixSeparator: String = "_") : Dataset[Row] = {
    import org.apache.spark.sql.functions.{col,explode}

    def mustFlatten(sc: StructType): Boolean =
      sc.fields.exists(f => f.dataType.isInstanceOf[ArrayType] || f.dataType.isInstanceOf[StructType])

    def flattenAndExplodeOne(sc: StructType, parent: Column = null, prefix: String = null, cols: Array[(DataType,Column)] = Array[(DataType,Column)]()): Array[(DataType,Column)] = {
      val res = sc.fields.foldLeft(cols)( (columns, f) => {
        val my_col = if (parent == null) col(f.name) else parent.getItem(f.name)
        val flat_name = if (prefix == null) f.name else s"${prefix}${prefixSeparator}${f.name}"
        f.dataType match {
          case st: StructType => flattenAndExplodeOne(st, my_col, flat_name, columns)

          case dt: ArrayType => {
            if (columns.exists(_._1.isInstanceOf[ArrayType])) {
              columns :+ ((dt,  my_col.as(flat_name)))
            } else {
              columns :+ ((dt, explode(my_col).as(flat_name)))
            }
          }
          case dt => columns :+ ((dt, my_col.as(flat_name)))
        }
      })
      res
    }

    var flatDf = df
    while (mustFlatten(flatDf.schema)) {
      val newColumns = flattenAndExplodeOne(flatDf.schema, null, null).map(_._2)
      flatDf = flatDf.select(newColumns:_*)
    }

    flatDf
  }
}

生成的 df2 具有以下架构和数据:

df2.printSchema
root
 |-- E2EDP01008GRP_E2EDPT1001GRP_E2EDPT2001_DATAHEADERCOLUMN_DOCNUM: long (nullable = true)
 |-- E2EDP01008GRP__xmlns: string (nullable = true)


df2.show(true)
+--------------------------------------------------------------+--------------------+
|E2EDP01008GRP_E2EDPT1001GRP_E2EDPT2001_DATAHEADERCOLUMN_DOCNUM|E2EDP01008GRP__xmlns|
+--------------------------------------------------------------+--------------------+
|                                                     141036013|http://Microsoft....|
|                                                     141036013|http://Microsoft....|
+--------------------------------------------------------------+--------------------+

【讨论】:

  • 这确实解决了我原来的问题。谢谢!但是在更复杂的情况下运行它,我收到此错误:“每个选择子句只允许一个生成器,但发现 7 个:explode(E2EDK02)、explode(E2EDK03)、explode(E2EDK04001)、explode(E2EDK14)、explode(E2EDKA1003GRP) ,爆炸(E2EDP01008GRP),爆炸(E2EDS01);“如果您知道对此的快速解决方法,请告诉我。否则,我会提出一个新问题。
  • 现在 flattenSchema 允许在同一级别进行任意数量的爆炸,但 spark 每次选择只允许 1 次爆炸,因此 flattenSchema 必须在第一次爆炸展开后短路转换,并将其他转换推迟到下一次迭代。我明天可以尝试更新最通用案例的答案,但它可能会使代码变得相当复杂
  • 我已经用更新版本更新了代码,应该避免在同一步骤中多次爆炸。
  • 我们如何处理带有空数据的arraytype,explode函数将返回所有空行,我知道我们可以使用explode_outer。如果我使用的是 spark 2.1,我们没有 explode_outer 。谢谢
猜你喜欢
  • 1970-01-01
  • 2021-02-22
  • 1970-01-01
  • 2017-04-15
  • 1970-01-01
  • 2022-12-12
  • 1970-01-01
  • 1970-01-01
  • 2017-11-15
相关资源
最近更新 更多