web-dev-qa-db-fra.com

Lecture de CSV dans un Spark Dataframe avec horodatage et types de date

C'est CDH avec Spark 1.6 .

J'essaie d'importer ce CSV hypothétique dans un Apache Spark DataFrame:

$ hadoop fs -cat test.csv
a,b,c,2016-09-09,a,2016-11-11 09:09:09.0,a
a,b,c,2016-09-10,a,2016-11-11 09:09:10.0,a

J'utilise databricks-csv pot.

val textData = sqlContext.read
    .format("com.databricks.spark.csv")
    .option("header", "false")
    .option("delimiter", ",")
    .option("dateFormat", "yyyy-MM-dd HH:mm:ss")
    .option("inferSchema", "true")
    .option("nullValue", "null")
    .load("test.csv")

J'utilise inferSchema pour créer le schéma du DataFrame résultant. La fonction printSchema () me donne la sortie suivante pour le code ci-dessus:

scala> textData.printSchema()
root
 |-- C0: string (nullable = true)
 |-- C1: string (nullable = true)
 |-- C2: string (nullable = true)
 |-- C3: string (nullable = true)
 |-- C4: string (nullable = true)
 |-- C5: timestamp (nullable = true)
 |-- C6: string (nullable = true)

scala> textData.show()
+---+---+---+----------+---+--------------------+---+
| C0| C1| C2|        C3| C4|                  C5| C6|
+---+---+---+----------+---+--------------------+---+
|  a|  b|  c|2016-09-09|  a|2016-11-11 09:09:...|  a|
|  a|  b|  c|2016-09-10|  a|2016-11-11 09:09:...|  a|
+---+---+---+----------+---+--------------------+---+

La colonne C3 est de type String . Je veux que C3 ait le type de date . Pour l'obtenir à ce jour, j'ai essayé le code suivant.

val textData = sqlContext.read.format("com.databricks.spark.csv")
    .option("header", "false")
    .option("delimiter", ",")
    .option("dateFormat", "yyyy-MM-dd")
    .option("inferSchema", "true")
    .option("nullValue", "null")
    .load("test.csv")

scala> textData.printSchema
root
 |-- C0: string (nullable = true)
 |-- C1: string (nullable = true)
 |-- C2: string (nullable = true)
 |-- C3: timestamp (nullable = true)
 |-- C4: string (nullable = true)
 |-- C5: timestamp (nullable = true)
 |-- C6: string (nullable = true)

scala> textData.show()
+---+---+---+--------------------+---+--------------------+---+
| C0| C1| C2|                  C3| C4|                  C5| C6|
+---+---+---+--------------------+---+--------------------+---+
|  a|  b|  c|2016-09-09 00:00:...|  a|2016-11-11 00:00:...|  a|
|  a|  b|  c|2016-09-10 00:00:...|  a|2016-11-11 00:00:...|  a|
+---+---+---+--------------------+---+--------------------+---+

La seule différence entre ce code et le premier bloc est la ligne d'option dateFormat (j'utilise "yyyy-MM- jj " au lieu de " aaaa-MM-jj HH: mm: ss "). Maintenant, je reçois les deux C3 et C5 comme horodatages (C3 n'est toujours pas la date). Mais pour C5, la partie HH :: mm: ss est ignorée et apparaît comme des zéros dans les données.

Idéalement, je veux que C3 soit de type date, C5 soit de type horodatage et que sa partie HH: mm: ss ne soit pas ignorée. Ma solution ressemble maintenant à ceci. Je fais le csv en tirant des données en parallèle de ma base de données. Je m'assure que je tire toutes les dates comme horodatages (pas idéal). Ainsi, le test csv ressemble à ceci maintenant:

$ hadoop fs -cat new-test.csv
a,b,c,2016-09-09 00:00:00,a,2016-11-11 09:09:09.0,a
a,b,c,2016-09-10 00:00:00,a,2016-11-11 09:09:10.0,a

Ceci est mon code de travail final:

val textData = sqlContext.read.format("com.databricks.spark.csv")
    .option("header", "false")
    .option("delimiter", ",")
    .option("dateFormat", "yyyy-MM-dd HH:mm:ss")
    .schema(finalSchema)
    .option("nullValue", "null")
    .load("new-test.csv")

Ici, j'utilise le format d'horodatage complet ( "aaaa-MM-jj HH: mm: ss" ) dans dateFormat. Je crée manuellement l'instance finalSchema où c3 est la date et C5 est le type d'horodatage (types SQL Spark). J'applique ces schémas en utilisant la fonction schema (). La sortie ressemble à ceci:

scala> finalSchema
res4: org.Apache.spark.sql.types.StructType = StructType(StructField(C0,StringType,true), StructField(C1,StringType,true), StructField(C2,StringType,true), StructField(C3,DateType,true), StructField(C4,StringType,true), StructField(C5,TimestampType,true), StructField(C6,StringType,true))

scala> textData.printSchema()
root
 |-- C0: string (nullable = true)
 |-- C1: string (nullable = true)
 |-- C2: string (nullable = true)
 |-- C3: date (nullable = true)
 |-- C4: string (nullable = true)
 |-- C5: timestamp (nullable = true)
 |-- C6: string (nullable = true)


scala> textData.show()
+---+---+---+----------+---+--------------------+---+
| C0| C1| C2|        C3| C4|                  C5| C6|
+---+---+---+----------+---+--------------------+---+
|  a|  b|  c|2016-09-09|  a|2016-11-11 09:09:...|  a|
|  a|  b|  c|2016-09-10|  a|2016-11-11 09:09:...|  a|
+---+---+---+----------+---+--------------------+---+

Existe-t-il un moyen plus simple ou standard d'analyser un fichier csv (qui a à la fois le type de date et d'horodatage dans un spark dataframe?

Liens pertinents:
http://spark.Apache.org/docs/latest/sql-programming-guide.html#manually-specifying-options
https://github.com/databricks/spark-csv

19
Mihir Shinde

Avec une option d'inférence pour les cas non triviaux, elle ne retournera probablement pas le résultat attendu. Comme vous pouvez le voir dans InferSchema.scala :

if (field == null || field.isEmpty || field == nullValue) {
  typeSoFar
} else {
  typeSoFar match {
    case NullType => tryParseInteger(field)
    case IntegerType => tryParseInteger(field)
    case LongType => tryParseLong(field)
    case DoubleType => tryParseDouble(field)
    case TimestampType => tryParseTimestamp(field)
    case BooleanType => tryParseBoolean(field)
    case StringType => StringType
    case other: DataType =>
      throw new UnsupportedOperationException(s"Unexpected data type $other")

Il essaiera seulement de faire correspondre chaque colonne avec un type d'horodatage, pas un type de date, donc la "solution prête à l'emploi" pour ce cas n'est pas possible. Mais d'après mon expérience, la solution "la plus facile" consiste à définir directement le schéma avec le type requis , cela évitera que l'option d'inférence définisse un type qui ne correspond que pour le RDD évalué et non pour l'ensemble des données. Votre schéma final est une solution efficace.

3
Jader Martins

Ce n'est pas vraiment élégant mais vous pouvez convertir de l'horodatage à ce jour comme ceci (vérifiez la dernière ligne):

val textData = sqlContext.read.format("com.databricks.spark.csv")
    .option("header", "false")
    .option("delimiter", ",")
    .option("dateFormat", "yyyy-MM-dd")
    .option("inferSchema", "true")
    .option("nullValue", "null")
    .load("test.csv")
    .withColumn("C4", expr("""to_date(C4)"""))
0
Carlos Verdes