如何在Spark中按日期和时间值对列进行排序?

7

注意: 我在Spark中有一个Dataframe。 这些时间/日期值构成了Dataframe中的单个列。

输入:

04-NOV-16 03.36.13.000000000 PM
06-NOV-15 03.42.21.000000000 PM
05-NOV-15 03.32.05.000000000 PM
06-NOV-15 03.32.14.000000000 AM

期望输出:

05-NOV-15 03.32.05.000000000 PM
06-NOV-15 03.32.14.000000000 AM
06-NOV-15 03.42.21.000000000 PM
04-NOV-16 03.36.13.000000000 PM

你尝试过什么吗? - mtoto
是的。尝试了强制类型转换,但没有成功。 - Dasarathy D R
Scala。由于它是字符串格式,因此尝试进行强制转换。 - Dasarathy D R
但是它没有成功。 - Dasarathy D R
2个回答

17

由于此格式不是标准格式,您需要使用unix_timestamp函数来解析字符串并将其转换为时间戳类型:

import org.apache.spark.sql.functions._

// Example data
val df = Seq(
  Tuple1("04-NOV-16 03.36.13.000000000 PM"),
  Tuple1("06-NOV-15 03.42.21.000000000 PM"),
  Tuple1("05-NOV-15 03.32.05.000000000 PM"),
  Tuple1("06-NOV-15 03.32.14.000000000 AM")
).toDF("stringCol")

// Timestamp pattern found in string
val pattern = "dd-MMM-yy hh.mm.ss.S a"

// Creating new DataFrame and ordering
val newDF = df
  .withColumn("timestampCol", unix_timestamp(df("stringCol"), pattern).cast("timestamp"))
  .orderBy("timestampCol")

newDF.show(false)

结果:

+-------------------------------+---------------------+
|stringCol                      |timestampCol         |
+-------------------------------+---------------------+
|05-NOV-15 03.32.05.000000000 PM|2015-11-05 15:32:05.0|
|06-NOV-15 03.32.14.000000000 AM|2015-11-06 03:32:14.0|
|06-NOV-15 03.42.21.000000000 PM|2015-11-06 15:42:21.0|
|04-NOV-16 03.36.13.000000000 PM|2016-11-04 15:36:13.0|
+-------------------------------+---------------------+

有关unix_timestamp和其他实用函数的详细信息可以在此处找到。

要构建时间戳格式,可以参考SimpleDateFormatter文档


编辑1:正如pheeleeppoo所说,您可以直接按表达式排序,而不是创建一个新列,假设您只想在数据框中保留字符串类型的列:

val newDF = df.orderBy(unix_timestamp(df("stringCol"), pattern).cast("timestamp"))

编辑2:请注意,unix_timestamp函数的精度为秒,如果毫秒非常重要,则可以使用UDF:

def myUDF(p: String) = udf(
  (value: String) => {
    val dateFormat = new SimpleDateFormat(p)
    val parsedDate = dateFormat.parse(value)
    new java.sql.Timestamp(parsedDate.getTime())
  }
)

val pattern = "dd-MMM-yy hh.mm.ss.S a"
val newDF = df.withColumn("timestampCol", myUDF(pattern)(df("stringCol"))).orderBy("timestampCol")

@Daniel de Paula:感谢您提供了上述日期格式的模式,但是如果日期中有上午或下午,这个模式如何工作? - Shankar
2
@Shankar,模式中的小写“hh”指定小时应在0到12之间。然后,模式中的最后一个“a”指定解析器将查找“AM”或“PM”,从而定义正确的时间。这是有关该模式的文档:https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html - Daniel de Paula
@DanieldePaula:感谢您的解释,我还有一个问题,实际上转换后的时间戳未正确显示毫秒数,即使我更改输入以包含一些毫秒数,它仍然始终显示为0,这是预期的吗? - Shankar
1
@Shankar unix_timestamp 函数返回的结果精度为秒,所以毫秒会被截断。如果你确实需要毫秒级别的精度,可能需要使用 UDF 或将 DataFrame 转换为 Dataset[String] 以便使用 map 函数。 - Daniel de Paula
@DanieldePaula:谢谢,您的意思是我可以使用DateTimeFormat模式,并将字符串转换为带有毫秒的时间戳? - Shankar
@Shankar 是的,我会在我的回答中添加一个部分。 - Daniel de Paula

3
你可以将 string 转换为 timestamp 后使用 sort 函数:
   df.sort(unix_timestamp(df("dateColumn"), "dd-MMM-yy hh.mm.ss.S a").cast("timestamp"))
     .show(false)

几乎没有什么区别,和我的答案一样。orderBysort基本上是相同的,不同之处在于前者可以在spark < 2.0中使用。更有价值的是,作为评论来改进其他答案,说unix_timestamp函数可以直接在orderBy中使用,而不是在withColumn中使用。 - Daniel de Paula
这其实是我的目标。 - pheeleeppoo
2
似乎重点在于“sort”函数。此外,模式不正确,大写的 H 表示 24 小时制。 - Daniel de Paula

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接