从数据集中的地图按键排序

时间:2019-05-01 10:57:48

标签: scala apache-spark apache-spark-dataset

我想按时间戳排序我从HDFS中检索的一些Avro文件。

我的avro文件的架构是:

  

标题:Map [String,String],正文:String

现在最棘手的部分是时间戳是地图中键/值之一。所以我在地图中包含了这样的时间戳:

  

key_1-> value_1,key_2-> value_2,时间戳记-> 1234567,key_n->   value_n

请注意,值的类型为字符串。

我创建了一个案例类来使用以下模式创建数据集:

case class Root(headers : Map[String,String], body: String)

创建我的数据集:

val ds = spark
          .read
          .format("com.databricks.spark.avro")
          .load(pathToHDFS)
          .as[Root]

我真的不知道如何开始这个问题,因为我只能获取列标题和正文。如何获取嵌套值以最终按时间戳排序?

我想做这样的事情:

ds.select("headers").doSomethingToGetTheMapStructure.doSomeConversionStringToTimeStampForTheColumnTimeStamp("timestamp").orderBy("timestamp")

有点精度:我不想从初始数据集中丢失任何数据,而只是进行排序操作。

我使用Spark 2.3.0。

3 个答案:

答案 0 :(得分:2)

您可以使用Scala的sortBy,它具有一个功能。我建议您将val ds明确声明为Vector(或其他集合),这样一来,您将在IntelliJ中看到适用的函数(如果使用的是IntelliJ),并且肯定可以编译。

请根据您的代码在下面查看我的示例:

  case class Root(headers : Map[String,String], body: String)

  val ds: Vector[Root] = spark
    .read
    .format("com.databricks.spark.avro")
    .load(pathToHDFS)
    .as[Root]

  val sorted = ds.sortBy(r => r.headers.get("timestamp").map(PROCESSING) ).reverse

编辑:添加了反向(假设您希望它降序)。在作为参数传递的函数内部,还将处理置于时间戳记。

答案 1 :(得分:1)

已加载的Dataset的外观应类似于以下示例数据集:

case class Root(headers : Map[String, String], body: String)

val ds = Seq(
  Root(Map("k11"->"v11", "timestamp"->"1554231600", "k12"->"v12"), "body1"),
  Root(Map("k21"->"v21", "timestamp"->"1554134400", "k22"->"v22"), "body2")
).toDS

您可以通过Map键简单地查询timestamp,将cast的值Long进行查找,然后执行orderBy,如下所示:

ds.
  withColumn("ts", $"headers"("timestamp").cast("Long")).
  orderBy("ts").
  show(false)
// +-------------------------------------------------+-----+----------+
// |headers                                          |body |ts        |
// +-------------------------------------------------+-----+----------+
// |[k21 -> v21, timestamp -> 1554134400, k22 -> v22]|body2|1554134400|
// |[k11 -> v11, timestamp -> 1554231600, k12 -> v12]|body1|1554231600|
// +-------------------------------------------------+-----+----------+

请注意,$"headers"("timestamp")与使用apply列方法(即$"headers".apply("timestamp"))相同。

或者,您也可以使用getItem通过密钥访问Map,例如:

$"headers".getItem("timestamp")

答案 2 :(得分:0)

import org.apache.spark.sql.{Encoders, Encoder, Dataset}
import org.apache.spark.sql.functions.{col, desc}
import java.sql.Timestamp

case class Nested(key_1: String,key_2: String,timestamp: Timestamp,key_n: String)
case class Root(headers:Nested,body:String)

implicit val rootCodec: Encoder[Root] = Encoders.product[Root]

val avroDS:Dataset[Root] = spark.read
                                .format("com.databricks.spark.avro")
                                .load(pathToHDFS)
                                .as[Root]

val sortedDF: DataFrame = avroDS.orderBy(desc(col("timestamp")))

此代码段会将您的Avro数据直接投射到Dataset[Root]。您将不必依靠导入sparksession.implicits,并且省去了将时间戳字段强制转换为 TimestampType 的步骤。在内部,Spark的Timestamp数据类型是使用java.sql.Timestamp实现的。