Исходное значение Искры 2
Получение этой нулевой ошибки в искровом окне Dataset.filter
Входной 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()
Возвращает нулевую ошибку
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)
Отсутствующее поле представляется как SQL NULL
peopleDF.where($"age".isNull).show
+----+----+----+
|name| age|stat|
+----+----+----+
| xyz|null| s|
+----+----+----+
Затем вы конвертируете Dataset[Row]
в Dataset[Person]
, который использует Long
для кодирования поля age
. Long
в 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]
вообще не влияет на схему.
При запросе Dataset
с использованием SQL (в зарегистрированной таблице) или DataFrame
API Spark не будет десериализовать объект. Поскольку схема все еще nullable
, мы можем выполнить:
peopleDS.where($"age" > 30).show
+----+---+----+
|name|age|stat|
+----+---+----+
+----+---+----+
без каких-либо проблем. Это всего лишь простая логика SQL, а NULL
- допустимое значение.
Когда мы используем статически типизированный Dataset
API:
peopleDS.filter(_.age > 30)
Искра должна десериализовать объект. Поскольку Long
не может быть NULL
(SQL NULL
), он не работает с исключением, которое вы видели.
Если бы не это, вы бы получили NPE.
Правильное статически типизированное представление ваших данных должно использовать Optional
types:
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
или нет.
Связанный Spark 2.0 Dataset vs DataFrame
Еще в рубрике
- Вопросы
- Apache-spark-dataset
- Исходное значение Искры 2