it-roy-ru.com

Лучший способ конвертировать строковое поле в метку времени в Spark

У меня есть CSV, в котором поле даты и времени в определенном формате. Я не могу импортировать его непосредственно в мой Dataframe, потому что это должна быть временная метка. Поэтому я импортирую его в виде строки и преобразую в Timestamp, как это

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)
}

Есть ли лучший, более краткий способ сделать это с помощью API Dataframe или spark-sql? Приведенный выше метод требует создания RDD и повторного предоставления схемы для Dataframe.

18
user568109

Spark> = 2.2

Начиная с версии 2.2, вы можете указать формат строки напрямую:

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  |#[email protected]#@#             |null               |
// +---+-------------------+-------------------+

Spark> = 1.6, <2.2

Вы можете использовать функции обработки даты, которые были представлены в Spark 1.5. Предполагая, что у вас есть следующие данные:

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

Вы можете использовать unix_timestamp для разбора строк и приведения их к метке времени

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  |#[email protected]#@#             |null                 |
// +---+-------------------+---------------------+

Как видите, он охватывает как разбор, так и обработку ошибок. Строка формата должна быть совместима с Java SimpleDateFormat .

Spark> = 1,5, <1,6 

Вы должны будете использовать что-то вроде этого:

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

или же

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

из-за СПАРК-11724 .

Spark <1,5

вы должны быть в состоянии использовать их с expr и HiveContext.

42
zero323

Я еще не играл с Spark SQL, но думаю, что это было бы более идиоматическим scala (нулевое использование не считается хорошей практикой):

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
    }    
  }
}

Обратите внимание, я предполагаю, что вы заранее знаете типы элементов Row (если вы читаете его из файла csv, все они String), поэтому я использую правильный тип, например String, а не Any (все является подтипом Any).

Это также зависит от того, как вы хотите обрабатывать исключения. В этом случае, если возникает исключение синтаксического анализа, None просто возвращается.

Вы можете использовать его дальше с:

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

В моем наборе данных есть отметка времени ISO8601, и мне нужно было преобразовать ее в формат «гггг-мм-дд». Вот что я сделал:

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"))

И вы можете просто использовать UDF в своем искровом SQL-запросе.

1
zengr

Я бы использовал https://github.com/databricks/spark-csv

Это выведет временные метки для вас.

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

Я хотел бы переместить написанный вами метод getTimeStamp в mapDartitions rdd и повторно использовать GenericMutableRow среди строк в итераторе:

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
    }
  }
}

И вы все равно должны использовать схему для создания DataFrame

val df = sqlContext.createDataFrame(rowRdd, tableSchema)

Использование GenericMutableRow внутри реализации итератора можно найти в Aggregate Operator , InMemoryColumnarTableScan , ParquetTableOperations и т.д.

0
Yijie Shen

У меня были некоторые проблемы с to_timestamp, когда он возвращал пустую строку. После долгих проб и ошибок я смог обойти это, применив метку времени, а затем вернув обратно в виде строки. Я надеюсь, что это поможет кому-то еще с той же проблемой:

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