我正在尝试在下面的df上运行udf udfTest
,但它正在运行
空指针异常可能是由于date1列中的空值。下面的代码需要进行哪些更改才能处理此异常?
+-----------+----------------+----------+
|info |date1 | date2 |
+-----------+----------------+----------+
| 11111| 2018-04-13|2018-05-20|
| 11111| |2018-05-20|
| 22222| 2018-05-01|2018-05-18|
| 22222| |2018-05-20|
| 33333| 2020-05-03|2018-05-18|
| 33333| |2017-04-17|
| 12931| 2018-05-07|2018-05-07|
| 12931| 2018-05-03|2018-05-04|
| 12931| 2018-05-20|2018-05-26|
| 12931| 2008-05-03|2018-05-20|
+-----------+----------------+----------+
def get_value(info: String, date1: String, date2: String): String = {
var str1: String = null
if (info == "11111" && date1 != null) {
str1 = date1
} else if (info == "22222" && date1 != null) {
str1 = date1
} else if (info == "33333" && date2 != null) {
str1 = date2
} else
str1 = null
str1
}
val udfTest = udf((id: String, date1: String, date2: String) => {
get_value(id: String, date1: String, date2: String)
})
df.withColumn("date3", udfTest(df("info"),df("date1"), df("date2")))
错误:
Caused by: java.lang.NullPointerException
at java.lang.String.<init>(String.java:152)
at get_value(<console>:61)
答案 0 :(得分:4)
您可以考虑使用Option&to;来封装空值。在scala代码中散落的空洞通常被认为是代码气味。或许,这种效果可能有所帮助。
val getValue: (String, String, String) => Option[String] = { (info, date1, date2) =>
(info, Option(date1), Option(date2)) match {
case ("11111", Some(d1), _) => Some(d1)
case ("22222", Some(d1), _) => Some(d1)
case ("33333", _, Some(d2)) => Some(d2)
case _ => None
}
}
然后您可以在UDF中使用它,如下所示,
val udfTest = udf(getValue)
df.withColumn("optional", udfTest(df("info"), df("date1"), df("date2"))).show()
请注意,现在而不是String列,您的数据框中有一个Option [String]列。
Here's scala中可选数据类型的有趣指南,以及有趣的用例。
编辑:
这样的事情可以解决你关于翻译假日spark sql代码的问题
private val yyyyMMddFormat = new SimpleDateFormat("yyyy-MM-dd")
private val days = List("Saturday", "Sunday")
private val holidays = List(
getPreviousDay("2018-05-22"),
getPreviousDay("2018-06-01")
)
def isPreviousDayAHoliday(date: String, days: List[String], holidays: List[java.sql.Date]): Boolean = {
val previousDay = getPreviousDay(date)
val eeeeFormat = new SimpleDateFormat("EEEE")
val dayOfPreviousDay = eeeeFormat.format(previousDay)
days.contains(dayOfPreviousDay) || holidays.contains(previousDay)
}
def getPreviousDay(date: String): java.sql.Date = {
new java.sql.Date(yyyyMMddFormat.parse(date).getTime - DAYS.toMillis(1))
}
现在你可以用这个作为后卫了。
(info, Option(date1), Option(date2)) match {
case ("12391", _, Some(d2)) if isPreviousDayAHoliday(d2, days, holidays) => Some(yyyyMMddFormat.format(getPreviousDay(d2)))
case _ => None
}
希望这有帮助
答案 1 :(得分:2)
像往常一样,当有内置功能的替代品时,我总是建议不要使用 udf
函数。由于udf
函数需要对每一行进行序列化和反序列化,因此它不如内置函数有效。
您可以使用when/otherwise
,isnull
和not
内置函数来实现您的要求
import org.apache.spark.sql.functions._
df.withColumn("date3",
when(not(isnull(col("date1"))) && col("info") === "11111", col("date1")).otherwise(
when(not(isnull(col("date1"))) && col("info") === "22222", col("date1")).otherwise(
when(not(isnull(col("date2"))) && col("info") === "33333", col("date2")).otherwise(lit(null))
)
)
)
或者只使用when/otherwise
和isNotNull
函数作为
import org.apache.spark.sql.functions._
df.withColumn("date3",
when(col("date1").isNotNull && col("info") === "11111", col("date1")).otherwise(
when(col("date1").isNotNull && col("info") === "22222", col("date1")).otherwise(
when(col("date2").isNotNull && col("info") === "33333", col("date2")).otherwise(lit(null))
)
)
)
答案 2 :(得分:1)
为了防止空指针异常,您可以将String常量与info变量进行比较,而不是相反。你真的不需要str1变量。至少与您的建议不同的工作解决方案是:
def get_value(info: String, date1: String, date2: String): String = {
if ("11111" == info && date1 != null) {
date1
} else if ("22222" == info && date1 != null) {
date1
} else if ("33333" == info && date2 != null) {
date2
} else
"null"
}
答案 3 :(得分:0)
使用Option
让它发挥作用。
def get_value(info: String, date1: String, date2: String): String = {
var str1: String = null
if ("11111" == info && date1 != null) {
Option(tdate1).getOrElse("null")
} else if ("22222" == info && date1 != null) {
Option(tdate1).getOrElse("null")
} else if ("33333" == info && date2 != null) {
Option(tdate2).getOrElse("null")
} else
Option(str1).getOrElse("null")
}