如何以自定义格式加载带时间戳的CSV?

时间:2017-04-06 15:28:10

标签: apache-spark apache-spark-sql hortonworks-data-platform hdinsight

我在csv文件中有一个时间戳字段,我使用spark csv库加载到数据帧。同一段代码在我的本地机器上使用Spark 2.0版本但在Azure Hortonworks HDP 3.5和3.6上引发错误。

我已经检查过,并且Azure HDInsight 3.5也使用相同的Spark版本,所以我不认为它是Spark版本的问题。

import org.apache.spark.sql.types._
val sourceFile = "C:\\2017\\datetest"
val sourceSchemaStruct = new StructType()
  .add("EventDate",DataTypes.TimestampType)
  .add("Name",DataTypes.StringType)
val df = spark.read
  .format("com.databricks.spark.csv")
  .option("header","true")
  .option("delimiter","|")
  .option("mode","FAILFAST")
  .option("inferSchema","false")
  .option("dateFormat","yyyy/MM/dd HH:mm:ss.SSS")
  .schema(sourceSchemaStruct)
  .load(sourceFile)

整个例外情况如下:

Caused by: java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
  at java.sql.Timestamp.valueOf(Timestamp.java:237)
  at org.apache.spark.sql.catalyst.util.DateTimeUtils$.stringToTime(DateTimeUtils.scala:179)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply$mcJ$sp(UnivocityParser.scala:142)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply(UnivocityParser.scala:142)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply(UnivocityParser.scala:142)
  at scala.util.Try.getOrElse(Try.scala:79)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13.apply(UnivocityParser.scala:139)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13.apply(UnivocityParser.scala:135)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser.org$apache$spark$sql$execution$datasources$csv$UnivocityParser$$nullSafeDatum(UnivocityParser.scala:179)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9.apply(UnivocityParser.scala:135)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9.apply(UnivocityParser.scala:134)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser.org$apache$spark$sql$execution$datasources$csv$UnivocityParser$$convert(UnivocityParser.scala:215)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser.parse(UnivocityParser.scala:187)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$5.apply(UnivocityParser.scala:304)
  at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$5.apply(UnivocityParser.scala:304)
  at org.apache.spark.sql.execution.datasources.FailureSafeParser.parse(FailureSafeParser.scala:61)
  ... 27 more

csv文件只有一行,如下所示:

"EventDate"|"Name"
"2016/12/19 00:43:27.583"|"adam"

2 个答案:

答案 0 :(得分:8)

TL; DR 使用timestampFormat选项(不是dateFormat)。

我设法在最新的Spark版本 2.3.0-SNAPSHOT (由主人建造)中重现它。

// OS shell
$ cat so-43259485.csv
"EventDate"|"Name"
"2016/12/19 00:43:27.583"|"adam"

// spark-shell
scala> spark.version
res1: String = 2.3.0-SNAPSHOT

case class Event(EventDate: java.sql.Timestamp, Name: String)
import org.apache.spark.sql.Encoders
val schema = Encoders.product[Event].schema

scala> spark
  .read
  .format("csv")
  .option("header", true)
  .option("mode","FAILFAST")
  .option("delimiter","|")
  .schema(schema)
  .load("so-43259485.csv")
  .show(false)
17/04/08 11:03:42 ERROR Executor: Exception in task 0.0 in stage 7.0 (TID 7)
java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
    at java.sql.Timestamp.valueOf(Timestamp.java:237)
    at org.apache.spark.sql.catalyst.util.DateTimeUtils$.stringToTime(DateTimeUtils.scala:167)
    at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply$mcJ$sp(UnivocityParser.scala:146)
    at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply(UnivocityParser.scala:146)
    at org.apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply(UnivocityParser.scala:146)
    at scala.util.Try.getOrElse(Try.scala:79)

corresponding line in the Spark sources是"根本原因"问题:

Timestamp.valueOf(s)

阅读javadoc of Timestamp.valueOf后,您可以了解论证应该是:

  

格式为yyyy-[m]m-[d]d hh:mm:ss[.f...]的时间戳。可以省略小数秒。 mm和dd的前导零也可以省略。

注意"小数秒可以省略"因此,首先将EventDate作为字符串加载,然后在删除不需要的小数秒后将其转换为时间戳。

val eventsAsString = spark.read.format("csv")
  .option("header", true)
  .option("mode","FAILFAST")
  .option("delimiter","|")
  .load("so-43259485.csv")

如果已定义,则for fields of TimestampType type Spark uses timestampFormat option首先被定义,并且仅在未使用the code the uses Timestamp.valueOf时才会生效。

事实证明,修复只是使用timestampFormat选项(而不是dateFormat!)。

val df = spark.read
  .format("com.databricks.spark.csv")
  .option("header","true")
  .option("delimiter","|")
  .option("mode","FAILFAST")
  .option("inferSchema","false")
  .option("timestampFormat","yyyy/MM/dd HH:mm:ss.SSS")
  .schema(sourceSchemaStruct)
  .load(sourceFile)
scala> df.show(false)
+-----------------------+----+
|EventDate              |Name|
+-----------------------+----+
|2016-12-19 00:43:27.583|adam|
+-----------------------+----+

Spark 2.1.0

使用自定义inferSchema的{​​{1}}选项在CSV中使用架构推理。

使用timestampFormat生效inferSchema来触发架构推断非常重要。

timestampFormat

"不正确"初始版本用于学习目的

val events = spark.read
  .format("csv")
  .option("header", true)
  .option("mode","FAILFAST")
  .option("delimiter","|")
  .option("inferSchema", true)
  .option("timestampFormat", "yyyy/MM/dd HH:mm:ss")
  .load("so-43259485.csv")

scala> events.show(false)
+-------------------+----+
|EventDate          |Name|
+-------------------+----+
|2016-12-19 00:43:27|adam|
+-------------------+----+

scala> events.printSchema
root
 |-- EventDate: timestamp (nullable = true)
 |-- Name: string (nullable = true)

Spark 2.2.0

从Spark 2.2开始,您可以使用val events = eventsAsString .withColumn("date", split($"EventDate", " ")(0)) .withColumn("date", translate($"date", "/", "-")) .withColumn("time", split($"EventDate", " ")(1)) .withColumn("time", split($"time", "[.]")(0)) // <-- remove millis part .withColumn("EventDate", concat($"date", lit(" "), $"time")) // <-- make EventDate right .select($"EventDate" cast "timestamp", $"Name") scala> events.printSchema root |-- EventDate: timestamp (nullable = true) |-- Name: string (nullable = true) events.show(false) scala> events.show +-------------------+----+ | EventDate|Name| +-------------------+----+ |2016-12-19 00:43:27|adam| +-------------------+----+ 函数来执行字符串到时间戳的转换。

to_timestamp

答案 1 :(得分:0)

我搜索了这个问题,并发现了正式的Github问题页面https://github.com/databricks/spark-csv/pull/280,该页面修复了使用自定义日期格式解析数据的相关错误。我查看了一些源代码,并根据code找出了您设置inferSchema的问题原因,默认值为false,如下所示。

  

inferSchema:自动推断列类型。它需要对数据进行一次额外传递,默认情况下

请使用inferSchema使用true更改yyyy/MM/dd HH:mm:ss.SSS作为您的日期格式SimpleDateFormat