Spark:如果数据帧中不存在列,则返回空列

时间:2018-10-04 10:55:11

标签: apache-spark pyspark apache-spark-sql pyspark-sql

如下面的代码所示,我正在将JSON文件读取到一个数据框中,然后从该数据框中选择一些字段到另一个字段中。

df_record = spark.read.json("path/to/file.JSON",multiLine=True)

df_basicInfo = df_record.select(col("key1").alias("ID"), \
                                col("key2").alias("Status"), \
                                col("key3.ResponseType").alias("ResponseType"), \
                                col("key3.someIndicator").alias("SomeIndicator") \
                                )

问题是,有时JSON文件没有我尝试获取的某些键-例如ResponseType。因此最终会引发诸如以下的错误:

org.apache.spark.sql.AnalysisException: No such struct field ResponseType

如何解决此问题而又在阅读时不强制使用架构?当它不可用时,是否可以使其在该列下返回NULL?

how do I detect if a spark dataframe has a column确实提到了如何检测数据框中的列是否可用。但是,这个问题是关于如何使用该功能的。

4 个答案:

答案 0 :(得分:1)

使用has_column函数通过herezero323定义general guidelines about adding empty columns

from pyspark.sql.functions import lit, col, when
from pyspark.sql.types import *

if has_column(df_record, "key3.ResponseType"):
    df_basicInfo = df_record.withColumn("ResponseType", col("key3.ResponseType"))
else:
    # Adjust types according to your needs
    df_basicInfo = df_record.withColumn("ResponseType", lit(None).cast("string")) 

并为您需要的每一列重复,或者

df_record.withColumn(
   "ResponseType", 
   when(
       lit(has_column(df_record, "key3.ResponseType")),
       col("key3.ResponseType")
   ).otherwise(lit(None).cast("string"))

根据您的要求调整类型,并对其余列重复该过程。

或者定义一个涵盖所有所需类型的模式:

schema = StructType([
    StructField("key1", StringType()),
    StructField("key2", StringType()),
    StructField("key2", StructType([
        StructField("ResponseType", StringType()),
        StructField("someIndicator", StringType()),
    ]))
])

df_record = spark.read.schema(schema).json("path/to/file.JSON",multiLine=True)

(再次调整类型),然后使用您当前的代码。

答案 1 :(得分:1)

我遇到了同样的问题,我使用的方法与Thomas相似。 我的用户定义功能代码:

import org.apache.spark.sql.functions.udf
import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
import org.apache.spark.sql.Row

spark.udf.register("tryGet", (root:GenericRowWithSchema, fieldName: String) => {
    var buffer:Row = root

    if (buffer != null) {
      if (buffer.schema.fieldNames.contains(fieldName)) {
         buffer.getString(buffer.fieldIndex(fieldName))
      } else {
        null
      }
    }
    else {
      null
    }
})

然后是我的查询:

%sql

SELECT
  Id,
  Created,
  Payload.Type,
  tryGet(Payload, "Error") as Error,
FROM dataWithJson
WHERE Payload.Type = 'Action'

答案 2 :(得分:0)

Spark缺少一个简单的功能:struct_has(STRUCT, PATH)struct_get(STRUCT, PATH, DEFAULT),其中PATH使用点表示法。

所以我写了一个非常简单的UDF:

来自https://gist.github.com/ebuildy/3c9b2663d47f7b65fbc12cfb469ae19c

import org.apache.spark.sql.functions.udf
import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
import org.apache.spark.sql.Row

spark.udf.register("struct_def", (root:GenericRowWithSchema, path: String, defaultValue: String) => {

    var fields = path.split("\\.")
    var buffer:Row = root
    val lastItem = fields.last

    fields = fields.dropRight(1)

    fields.foreach( (field:String) => {
        if (buffer != null) {
            if (buffer.schema.fieldNames.contains(field)) {
                buffer = buffer.getStruct(buffer.fieldIndex(field))
            } else {
                buffer = null
            }
        }
    })

    if (buffer == null) {
        defaultValue
    } else {
        buffer.getString(buffer.fieldIndex(lastItem))
    }
})

这使您可以像这样查询:

SELECT struct_get(MY_COL, "foo.bar", "no") FROM DATA

答案 3 :(得分:0)

因此,我尝试使用接受的答案,但是我发现,如果列key3.ResponseType不存在,它将失败。

您可以执行以下操作-

def hasColumn(df: DataFrame, path: String) = 
  if (Try(df(path)).isSuccess == true) {
      df(path)
  }
  else {
      lit(null) 
  }

这里,您在函数中评估是否存在column,如果不存在,则仅返回NULL列。

您现在可以像这样使用它-

df_basicInfo = df_record.withColumn("ResponseType", hasColumn(df_record, "key3.ResponseType"))