web-dev-qa-db-fra.com

Meilleure façon de convertir un champ de chaîne en horodatage dans Spark

J'ai un fichier CSV dans lequel un champ est datetime dans un format spécifique. Je ne peux pas l'importer directement dans mon Dataframe, car il doit s'agir d'un horodatage. Donc, je l'importe sous forme de chaîne et le convertis en Timestamp comme ceci

import Java.sql.Timestamp
import Java.text.SimpleDateFormat
import Java.util.Date
import org.Apache.spark.sql.Row

def getTimestamp(x:Any) : Timestamp = {
    val format = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss")
    if (x.toString() == "") 
    return null
    else {
        val d = format.parse(x.toString());
        val t = new Timestamp(d.getTime());
        return t
    }
}

def convert(row : Row) : Row = {
    val d1 = getTimestamp(row(3))
    return Row(row(0),row(1),row(2),d1)
}

Existe-t-il un moyen plus concis de procéder plus efficacement avec l’API Dataframe ou spark-sql? La méthode ci-dessus nécessite la création d'un RDD et de redonner le schéma pour le Dataframe.

18
user568109

Spark> = 2.2

Depuis que vous 2.2, vous pouvez fournir directement la chaîne de format:

import org.Apache.spark.sql.functions.to_timestamp

val ts = to_timestamp($"dts", "MM/dd/yyyy HH:mm:ss")

df.withColumn("ts", ts).show(2, false)

// +---+-------------------+-------------------+
// |id |dts                |ts                 |
// +---+-------------------+-------------------+
// |1  |05/26/2016 01:01:01|2016-05-26 01:01:01|
// |2  |#$@#@#             |null               |
// +---+-------------------+-------------------+

Spark> = 1,6, <2,2

Vous pouvez utiliser les fonctions de traitement de date introduites dans Spark 1.5. En supposant que vous ayez les données suivantes:

val df = Seq((1L, "05/26/2016 01:01:01"), (2L, "#$@#@#")).toDF("id", "dts")

Vous pouvez utiliser unix_timestamp pour analyser les chaînes et les convertir en horodatage.

import org.Apache.spark.sql.functions.unix_timestamp

val ts = unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss").cast("timestamp")

df.withColumn("ts", ts).show(2, false)

// +---+-------------------+---------------------+
// |id |dts                |ts                   |
// +---+-------------------+---------------------+
// |1  |05/26/2016 01:01:01|2016-05-26 01:01:01.0|
// |2  |#$@#@#             |null                 |
// +---+-------------------+---------------------+

Comme vous pouvez le constater, il couvre à la fois l'analyse et la gestion des erreurs. La chaîne de format doit être compatible avec Java SimpleDateFormat .

Spark> = 1,5, <1,6 

Vous devrez utiliser quelque chose comme ceci:

unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss").cast("double").cast("timestamp")

ou

(unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss") * 1000).cast("timestamp")

en raison de SPARK-11724 .

Spark <1.5

vous devriez pouvoir les utiliser avec expr et HiveContext.

42
zero323

Je n'ai pas encore joué avec Spark SQL mais je pense que ce serait plus scala (l'utilisation de null n'est pas considérée comme une bonne pratique):

def getTimestamp(s: String) : Option[Timestamp] = s match {
  case "" => None
  case _ => {
    val format = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss")
    Try(new Timestamp(format.parse(s).getTime)) match {
      case Success(t) => Some(t)
      case Failure(_) => None
    }    
  }
}

Veuillez noter que je suppose que vous connaissez les types d'éléments Row à l'avance (si vous le lisez à partir d'un fichier csv, ils sont tous String), c'est pourquoi j'utilise un type correct comme String et non Any (tout est un sous-type de Any).

Cela dépend également de la façon dont vous souhaitez gérer les exceptions d'analyse. Dans ce cas, si une exception d'analyse se produit, une None est simplement renvoyée.

Vous pouvez l'utiliser plus tard avec:

rows.map(row => Row(row(0),row(1),row(2), getTimestamp(row(3))
6
jarandaf

J'ai un horodatage ISO8601 dans mon jeu de données et j'avais besoin de le convertir au format "aaaa-MM-jj". C'est ce que j'ai fait:

import org.joda.time.{DateTime, DateTimeZone}
object DateUtils extends Serializable {
  def dtFromUtcSeconds(seconds: Int): DateTime = new DateTime(seconds * 1000L, DateTimeZone.UTC)
  def dtFromIso8601(isoString: String): DateTime = new DateTime(isoString, DateTimeZone.UTC)
}

sqlContext.udf.register("formatTimeStamp", (isoTimestamp : String) => DateUtils.dtFromIso8601(isoTimestamp).toString("yyyy-MM-dd"))

Et vous pouvez simplement utiliser le fichier UDF dans votre requête spark SQL.

1
zengr

Je voudrais utiliser https://github.com/databricks/spark-csv

Cela déduira les horodatages pour vous.

import com.databricks.spark.csv._
val rdd: RDD[String] = sc.textFile("csvfile.csv")

val df : DataFrame = new CsvParser().withDelimiter('|')
      .withInferSchema(true)
      .withParseMode("DROPMALFORMED")
      .csvRdd(sqlContext, rdd)
0
mark

Je souhaite déplacer la méthode getTimeStamp écrite par vous dans mapPartitions de rdd et réutiliser GenericMutableRow entre les lignes d'un itérateur:

val strRdd = sc.textFile("hdfs://path/to/cvs-file")
val rowRdd: RDD[Row] = strRdd.map(_.split('\t')).mapPartitions { iter =>
  new Iterator[Row] {
    val row = new GenericMutableRow(4)
    var current: Array[String] = _

    def hasNext = iter.hasNext
    def next() = {
      current = iter.next()
      row(0) = current(0)
      row(1) = current(1)
      row(2) = current(2)

      val ts = getTimestamp(current(3))
      if(ts != null) {
        row.update(3, ts)
      } else {
        row.setNullAt(3)
      }
      row
    }
  }
}

Et vous devriez toujours utiliser le schéma pour générer un DataFrame

val df = sqlContext.createDataFrame(rowRdd, tableSchema)

L'utilisation de GenericMutableRow dans une implémentation d'itérateur peut être trouvée dans Opérateur Agrégat , InMemoryColumnarTableScan , ParquetTableOperations etc.

0
Yijie Shen

J'ai eu quelques problèmes avec to_timestamp où il retournait une chaîne vide. Après beaucoup d'essais et d'erreurs, j'ai pu contourner le problème en exprimant l'horodatage, puis en faisant une chaîne. J'espère que cela aidera tout le monde avec le même problème:

df.columns.intersect(cols).foldLeft(df)((newDf, col) => {
  val conversionFunc = to_timestamp(newDf(col).cast("timestamp"), "MM/dd/yyyy HH:mm:ss").cast("string")
  newDf.withColumn(col, conversionFunc)
})
0
ashwin319