spark Dataset.filterでこのnullエラーを取得する
入力CSV:
name,age,stat
abc,22,m
xyz,,s
作業コード:
case class Person(name: String, age: Long, stat: String)
val peopleDS = spark.read.option("inferSchema","true")
.option("header", "true").option("delimiter", ",")
.csv("./people.csv").as[Person]
peopleDS.show()
peopleDS.createOrReplaceTempView("people")
spark.sql("select * from people where age > 30").show()
失敗したコード(次の行を追加するとエラーが返されます):
val filteredDS = peopleDS.filter(_.age > 30)
filteredDS.show()
Nullエラーを返します
Java.lang.RuntimeException: Null value appeared in non-nullable field:
- field (class: "scala.Long", name: "age")
- root class: "com.gcp.model.Person"
If the schema is inferred from a Scala Tuple/case class, or a Java bean, please try to use scala.Option[_] or other nullable types (e.g. Java.lang.Integer instead of int/scala.Int).
あなたが得る例外はすべてを説明するべきですが、ステップバイステップで行きましょう:
csv
データソースを使用してデータをロードすると、すべてのフィールドがnullable
としてマークされます。
val path: String = ???
val peopleDF = spark.read
.option("inferSchema","true")
.option("header", "true")
.option("delimiter", ",")
.csv(path)
peopleDF.printSchema
root
|-- name: string (nullable = true)
|-- age: integer (nullable = true)
|-- stat: string (nullable = true)
欠落しているフィールドはSQLNULL
として表されます
peopleDF.where($"age".isNull).show
+----+----+----+
|name| age|stat|
+----+----+----+
| xyz|null| s|
+----+----+----+
次に、Dataset[Row]
をDataset[Person]
に変換します。これは、Long
を使用してage
フィールドをエンコードします。 Long
in Scalaはnull
にすることはできません。入力スキーマはnullable
であるため、出力スキーマはnullable
のままです。
val peopleDS = peopleDF.as[Person]
peopleDS.printSchema
root
|-- name: string (nullable = true)
|-- age: integer (nullable = true)
|-- stat: string (nullable = true)
as[T]
はスキーマにまったく影響を与えないことに注意してください。
SQL(登録済みテーブル上)またはDataset
APIを使用してDataFrame
をクエリすると、Sparkはオブジェクトを逆シリアル化しません。スキーマはまだnullable
実行できます:
peopleDS.where($"age" > 30).show
+----+---+----+
|name|age|stat|
+----+---+----+
+----+---+----+
問題なく。これは単なるSQLロジックであり、NULL
は有効な値です。
静的に型付けされたDataset
APIを使用する場合:
peopleDS.filter(_.age > 30)
Sparkはオブジェクトを逆シリアル化する必要があります。 Long
をnull
(SQL NULL
)にすることはできないため、これまで見てきた例外を除いて失敗します。
それがなかったら、あなたはNPEを手に入れるでしょう。
データの静的に型付けされた正しい表現では、Optional
型を使用する必要があります。
case class Person(name: String, age: Option[Long], stat: String)
調整されたフィルター機能付き:
peopleDS.filter(_.age.map(_ > 30).getOrElse(false))
+----+---+----+
|name|age|stat|
+----+---+----+
+----+---+----+
必要に応じて、パターンマッチングを使用できます。
peopleDS.filter {
case Some(age) => age > 30
case _ => false // or case None => false
}
name
とstat
にオプションの型を使用する必要はありません(ただし、とにかくお勧めします)。 Scala String
は単なるJava String
であるため、null
にすることができます。このアプローチでは、アクセスされた値がnull
であるかどうかを明示的に確認する必要があります。