如何根据列生成多个记录?

时间:2017-06-05 10:40:36

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

我有如下记录。如果第3个属性为EXTERNAL,我想将单个记录转换为两个值INTERNALAll的记录。

输入数据集:

Surender,cts,INTERNAL
Raja,cts,EXTERNAL
Ajay,tcs,All

预期产出:

Surender,cts,INTERNAL
Raja,cts,EXTERNAL
Ajay,tcs,INTERNAL
Ajay,tcs,EXTERNAL

我的火花代码:

case class Customer(name:String,organisation:String,campaign_type:String)

val custRDD = sc.textFile("/user/cloudera/input_files/customer.txt")

val mapRDD = custRDD.map(record => record.split(","))
    .map(arr => (arr(0),arr(1),arr(2))
    .map(tuple => {
      val name            = tuple._1.trim
      val organisation    = tuple._2.trim
      val campaign_type   = tuple._3.trim.toUpperCase
      Customer(name, organisation, campaign_type)
    })

mapRDD.toDF().registerTempTable("customer_processed")

sqlContext.sql("SELECT * FROM customer_processed").show

有人可以帮我解决这个问题吗?

2 个答案:

答案 0 :(得分:3)

您可以使用udf转换包含Seq字符串的campaign_type列,以将其映射到广告系列类型,然后explode

val campaignType_ : (String => Seq[String]) = {
  case s if s == "ALL" => Seq("EXTERNAL", "INTERNAL")
  case s => Seq(s)
}

val campaignType = udf(campaignType_)

val df = Seq(("Surender", "cts", "INTERNAL"),
  ("Raja", "cts", "EXTERNAL"),
  ("Ajay", "tcs", "ALL"))
  .toDF("name", "organisation", "campaign_type")

val step1 = df.withColumn("campaign_type", campaignType($"campaign_type"))
step1.show
// +--------+------------+--------------------+
// |    name|organisation|       campaign_type|
// +--------+------------+--------------------+
// |Surender|         cts|          [INTERNAL]|
// |    Raja|         cts|          [EXTERNAL]|
// |    Ajay|         tcs|[EXTERNAL, INTERNAL]|
// +--------+------------+--------------------+

val step2 = step1.select($"name", $"organisation", explode($"campaign_type"))
step2.show
// +--------+------------+--------+
// |    name|organisation|     col|
// +--------+------------+--------+
// |Surender|         cts|INTERNAL|
// |    Raja|         cts|EXTERNAL|
// |    Ajay|         tcs|EXTERNAL|
// |    Ajay|         tcs|INTERNAL|
// +--------+------------+--------+

修改

你实际上并不需要一个udf,你可以在step1上使用when()。否则谓词如下:

val step1 = df.withColumn("campaign_type", 
when(col("campaign_type") === "ALL", array("EXTERNAL", "INTERNAL")).otherwise(array(col("campaign_type")))

答案 1 :(得分:3)

因为它是斯卡拉......

如果你想编写一个更惯用的Scala代码(并且可能由于缺乏优化来交换一些性能以获得更惯用的代码),你可以使用flatMap运算符(删除隐式参数):

  

flatMap [U](func:(T)⇒TraversableOnce[U]):数据集[U] 首先将函数应用于此数据集的所有元素,然后展平,返回新的数据集结果。

注意:flatMap相当于explode功能,但您不必注册UDF(如另一个答案)。

解决方案如下:

// I don't care about the names of the columns since we use Scala
// as you did when you tried to write the code
scala> input.show
+--------+---+--------+
|     _c0|_c1|     _c2|
+--------+---+--------+
|Surender|cts|INTERNAL|
|    Raja|cts|EXTERNAL|
|    Ajay|tcs|     All|
+--------+---+--------+

val result = input.
  as[(String, String, String)].
  flatMap { case r @ (name, org, campaign) => 
    if ("all".equalsIgnoreCase(campaign)) {
      Seq("INTERNAL", "EXTERNAL").map { cname =>
        (name, org, cname)
      }
    } else Seq(r)
  }
scala> result.show
+--------+---+--------+
|      _1| _2|      _3|
+--------+---+--------+
|Surender|cts|INTERNAL|
|    Raja|cts|EXTERNAL|
|    Ajay|tcs|INTERNAL|
|    Ajay|tcs|EXTERNAL|
+--------+---+--------+

比较两个查询的性能,即基于flatMap的基于explode的查询,我认为基于explode - 可能会稍快一点,并且更好地优化,因为一些代码在Spark& #39; s控制(在逻辑运算符映射到物理couterpart之前使用它们)。在flatMap中,整个优化是您作为Scala开发人员的责任。

以下红色区域对应基于flatMap的代码,警告标志是非常昂贵的DeserializeToObjectSerializeFromObject运营商。

enter image description here

有趣的是每个查询的Spark作业数量及其持续时间。基于explode的查询似乎需要2个Spark作业和200 ms,而基于flatMap的查询只需要1个Spark作业和43 ms。

enter image description here

让我感到惊讶,并建议基于flatMap的查询可以更快(!)