spark csvライブラリを使用してデータフレームにロードするcsvファイルにタイムスタンプフィールドがあります。同じコードがローカルマシンでSpark 2.0バージョンですが、Azure Hortonworks HDP3.5および3.6でエラーがスローされます。
確認しましたが、Azure HDInsight3.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ファイルには次のように1行しかありません。
"EventDate"|"Name"
"2016/12/19 00:43:27.583"|"adam"
TL; DRtimestampFormat
オプションを使用します(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)
Sparkソース の対応する行は、問題の「根本原因」です。
Timestamp.valueOf(s)
Timestamp.valueOfのjavadoc を読んだら、引数は次のようになるはずであることがわかります。
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")
TimestampType
タイプのフィールドの場合 Spark timestampFormat
オプションを使用 定義されている場合は最初に使用しない場合のみ コードは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|
+-----------------------+----+
カスタムinferSchema
でtimestampFormat
オプションを使用して、CSVでスキーマ推論を使用します。
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)
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|
+-------------------+----+
Spark 2.2以降、to_timestamp
関数を使用して、文字列からタイムスタンプへの変換を行うことができます。
eventsAsString.select($"EventDate", to_timestamp($"EventDate", "yyyy/MM/dd HH:mm:ss.SSS")).show(false)
scala> eventsAsString.select($"EventDate", to_timestamp($"EventDate", "yyyy/MM/dd HH:mm:ss.SSS")).show(false)
+-----------------------+----------------------------------------------------+
|EventDate |to_timestamp(`EventDate`, 'yyyy/MM/dd HH:mm:ss.SSS')|
+-----------------------+----------------------------------------------------+
|2016/12/19 00:43:27.583|2016-12-19 00:43:27 |
+-----------------------+----------------------------------------------------+
この問題を検索し、公式のGithub問題ページを発見しました https://github.com/databricks/spark-csv/pull/28 カスタム日付形式でデータを解析するための関連バグを修正しました。いくつかのソースコードを確認し、 code に従って、以下のようにデフォルト値inferSchema
でfalse
に設定されている問題の理由を見つけました。
inferSchema
:列タイプを自動的に推測します。データに対して1回の追加パスが必要であり、デフォルトではfalse
日付形式をinferSchema
からtrue
に変更してくださいyyyy/MM/dd HH:mm:ss.SSS
SimpleDateFormat
を使用します。