如何在从另一个表连接列名的同时在spark中连接多个列(每行不同)

时间:2017-08-08 19:16:12

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

我正在尝试使用concat函数在spark中连接多个列。

例如,下面是我必须添加新的连接列

的表
table - **t**
+---+----+  
| id|name|
+---+----+  
|  1|   a|  
|  2|   b|
+---+----+

及以下是具有关于给定id连接哪些列的信息的表(对于id 1列id和名称需要连接而id 2仅为id)

table - **r**
+---+-------+
| id|   att |
+---+-------+
|  1|id,name|
|  2|   id  |
+---+-------+

如果我加入两个表并执行类似下面的操作,我可以连接但不能基于表r(因为新列有1,第一行有一个但第二行应该只有2)

t.withColumn("new",concat_ws(",",t.select("att").first.mkString.split(",").map(c => col(c)): _*)).show
+---+----+-------+---+
| id|name|  att  |new|
+---+----+-------+---+
|  1|   a|id,name|1,a|
|  2|   b|  id   |2,b|
+---+----+-------+---+

我必须在上面的查询中选择之前应用过滤器,但我不知道如何在每行的withColumn中执行此操作。

如果可能的话,如下所示。

t.withColumn("new",concat_ws(",",t.**filter**("id="+this.id).select("att").first.mkString.split(",").map(c => col(c)): _*)).show

因为它需要根据id过滤每一行。

scala> t.filter("id=1").select("att").first.mkString.split(",").map(c => col(c))
res90: Array[org.apache.spark.sql.Column] = Array(id, name)

scala> t.filter("id=2").select("att").first.mkString.split(",").map(c => col(c))
res89: Array[org.apache.spark.sql.Column] = Array(id)

以下是最终要求的结果。

+---+----+-------+---+
| id|name|  att  |new|
+---+----+-------+---+
|  1|   a|id,name|1,a|
|  2|   b|  id   |2  |
+---+----+-------+---+

2 个答案:

答案 0 :(得分:0)

这可以在UDF中完成:

val cols: Seq[Column] = dataFrame.columns.map(x => col(x)).toSeq
val indices: Seq[String] = dataFrame.columns.map(x => x).toSeq

val generateNew = udf((values: Seq[Any]) => {
  val att = values(indices.indexOf("att")).toString.split(",")
  val associatedIndices = indices.filter(x => att.contains(x))
  val builder: StringBuilder  = StringBuilder.newBuilder
  values.filter(x => associatedIndices.contains(values.indexOf(x)))
  values.foreach{ v => builder.append(v).append(";") }
  builder.toString()
})

val dfColumns = array(cols:_*)
val dNew = dataFrame.withColumn("new", generateNew(dfColumns))

这只是一个草图,但想法是你可以将一系列项目传递给用户定义的函数,并选择动态需要的项目。

请注意,您可以传递其他类型的集合/地图 - 例如How to pass array to UDF

答案 1 :(得分:0)

我们可以使用UDF

此逻辑的要求。

t 的列名应与表 r

中的col att 的列顺序相同
scala> input_df_1.show
+---+----+
| id|name|
+---+----+
|  1|   a|
|  2|   b|
+---+----+

scala> input_df_2.show
+---+-------+
| id|    att|
+---+-------+
|  1|id,name|
|  2|     id|
+---+-------+

scala> val join_df = input_df_1.join(input_df_2,Seq("id"),"inner")
join_df: org.apache.spark.sql.DataFrame = [id: int, name: string ... 1 more field]

scala> val req_cols = input_df_1.columns
req_cols: Array[String] = Array(id, name)

scala> def new_col_udf = udf((cols : Seq[String],row : String,attr : String) => {
     |     val row_values = row.split(",")
     |     val attrs = attr.split(",")
     |     val req_val = attrs.map{at =>
     |     val index = cols.indexOf(at)
     |     row_values(index)
     |     }
     |     req_val.mkString(",")
     |     })
new_col_udf: org.apache.spark.sql.expressions.UserDefinedFunction

scala>  val intermediate_df = join_df.withColumn("concat_column",concat_ws(",",'id,'name)).withColumn("new_col",new_col_udf(lit(req_cols),'concat_column,'att))
intermediate_df: org.apache.spark.sql.DataFrame = [id: int, name: string ... 3 more fields]

scala> val result_df = intermediate_df.select('id,'name,'att,'new_col)
result_df: org.apache.spark.sql.DataFrame = [id: int, name: string ... 2 more fields]

scala> result_df.show
+---+----+-------+-------+
| id|name|    att|new_col|
+---+----+-------+-------+
|  1|   a|id,name|    1,a|
|  2|   b|     id|      2|
+---+----+-------+-------+

希望它能回答你的问题。