将字符串字段转换为Spark中的时间戳的更好方法

时间:2015-04-24 09:45:28

标签: scala apache-spark apache-spark-sql

我有一个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)
}

使用Dataframe API或spark-sql有更好,更简洁的方法吗?上述方法需要创建RDD并再次为Dataframe提供架构。

7 个答案:

答案 0 :(得分:45)

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  |#$@#@#             |null               |
// +---+-------------------+-------------------+

Spark> = 1.6,< 2.2

您可以使用Spark 1.5中引入的日期处理功能。假设您有以下数据:

val df = Seq((1L, "05/26/2016 01:01:01"), (2L, "#$@#@#")).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  |#$@#@#             |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")

归因于SPARK-11724

Spark< 1.5

您应该能够将这些内容与exprHiveContext一起使用。

答案 1 :(得分:6)

我还没有使用Spark SQL,但我认为这将是更惯用的scala(null使用不被认为是一种好习惯):

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

答案 2 :(得分:1)

我的数据集中有ISO8601时间戳,我需要将其转换为“yyyy-MM-dd”格式。这就是我所做的:

In [8]:
df < 95

Out[8]:
                         2015-09-26 2015-09-27 2015-09-28 2015-09-29
0001_Durgacomplex_NBSNL        True      False       True       True
0002_Shivanagar_area_Bdr      False      False       True      False
0003_Old_city_Bidar           False       True      False      False
0004_Bidar_Mw_Station          True      False      False      False
0005_Bidri_colony             False       True      False      False

您可以在spark SQL查询中使用UDF。

答案 3 :(得分:1)

火花版本:2.4.4

scala> import org.apache.spark.sql.types.TimestampType
import org.apache.spark.sql.types.TimestampType

scala> val df = Seq("2019-04-01 08:28:00").toDF("ts")
df: org.apache.spark.sql.DataFrame = [ts: string]

scala> val df_mod = df.select($"ts".cast(TimestampType))
df_mod: org.apache.spark.sql.DataFrame = [ts: timestamp]

scala> df_mod.printSchema()
root
 |-- ts: timestamp (nullable = true)

答案 4 :(得分:0)

我想将您编写的getTimeStamp方法移到rdd的mapPartitions中,并在迭代器中的行中重用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 OperatorInMemoryColumnarTableScanParquetTableOperations等中找到。

答案 5 :(得分:0)

我会使用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)

答案 6 :(得分:0)

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