加入后如何避免重复列?

时间:2016-02-07 20:10:52

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

我有两个包含以下列的数据框:

df1.columns
//  Array(ts, id, X1, X2)

df2.columns
//  Array(ts, id, Y1, Y2)

我做完

val df_combined = df1.join(df2, Seq(ts,id))

我最终得到以下列:Array(ts, id, X1, X2, ts, id, Y1, Y2)。我可以预期公共列将被删除。是否有其他需要做的事情?

9 个答案:

答案 0 :(得分:27)

简单的答案(来自Databricks FAQ on this matter)是执行连接,其中连接的列表示为字符串数组(或一个字符串)而不是谓词。

下面是一个改编自Databricks FAQ的示例,但有两个连接列,以回答原始海报的问题。

以下是数据框:

val llist = Seq(("bob", "b", "2015-01-13", 4), ("alice", "a", "2015-04-23",10))

val left = llist.toDF("firstname","lastname","date","duration")

left.show()

/*
+---------+--------+----------+--------+
|firstname|lastname|      date|duration|
+---------+--------+----------+--------+
|      bob|       b|2015-01-13|       4|
|    alice|       a|2015-04-23|      10|
+---------+--------+----------+--------+
*/

以下是正确的数据框:

val right = Seq(("alice", "a", 100),("bob", "b", 23)).toDF("firstname","lastname","upload")

right.show()

/*
+---------+--------+------+
|firstname|lastname|upload|
+---------+--------+------+
|    alice|       a|   100|
|      bob|       b|    23|
+---------+--------+------+
*/

以下是不正确的解决方案,其中连接列定义为谓词left("firstname")===right("firstname") && left("lastname")===right("lastname")

错误的结果是firstnamelastname列在已连接的数据框中重复:

left.join(right, left("firstname")===right("firstname") &&
                 left("lastname")===right("lastname")).show

/*
+---------+--------+----------+--------+---------+--------+------+
|firstname|lastname|      date|duration|firstname|lastname|upload|
+---------+--------+----------+--------+---------+--------+------+
|      bob|       b|2015-01-13|       4|      bob|       b|    23|
|    alice|       a|2015-04-23|      10|    alice|       a|   100|
+---------+--------+----------+--------+---------+--------+------+
*/

正确解决方案是将连接列定义为字符串数组Seq("firstname", "lastname")。输出数据框没有重复的列:

left.join(right, Seq("firstname", "lastname")).show

/*
+---------+--------+----------+--------+------+
|firstname|lastname|      date|duration|upload|
+---------+--------+----------+--------+------+
|      bob|       b|2015-01-13|       4|    23|
|    alice|       a|2015-04-23|      10|   100|
+---------+--------+----------+--------+------+
*/

答案 1 :(得分:20)

这是预期的行为。 DataFrame.join方法等同于SQL连接

SELECT * FROM a JOIN b ON joinExprs

如果您想忽略重复的列,只需删除它们或之后选择感兴趣的列。如果您想消除歧义,可以使用父DataFrames

来访问这些内容
val a: DataFrame = ???
val b: DataFrame = ???
val joinExprs: Column = ???

a.join(b, joinExprs).select(a("id"), b("foo"))
// drop equivalent 
a.alias("a").join(b.alias("b"), joinExprs).drop(b("id")).drop(a("foo"))

或使用别名:

// As for now aliases don't work with drop
a.alias("a").join(b.alias("b"), joinExprs).select($"a.id", $"b.foo")

对于equi-joins,存在一种特殊的快捷语法,它采用a sequence of strings

val usingColumns: Seq[String] = ???

a.join(b, usingColumns)

single string

val usingColumn: String = ???

a.join(b, usingColumn)

只保留连接条件中使用的一列副本。

答案 2 :(得分:7)

我已经坚持了一段时间,直到最近我想出了一个非常简单的解决方案。

说a是

scala> val a  = Seq(("a", 1), ("b", 2)).toDF("key", "vala")
a: org.apache.spark.sql.DataFrame = [key: string, vala: int]

scala> a.show
+---+----+
|key|vala|
+---+----+
|  a|   1|
|  b|   2|
+---+----+
and 
scala> val b  = Seq(("a", 1)).toDF("key", "valb")
b: org.apache.spark.sql.DataFrame = [key: string, valb: int]

scala> b.show
+---+----+
|key|valb|
+---+----+
|  a|   1|
+---+----+

我可以这样做只选择数据框a:

中的值
scala> a.join(b, a("key") === b("key"), "left").select(a.columns.map(a(_)) : _*).show
+---+----+
|key|vala|
+---+----+
|  a|   1|
|  b|   2|
+---+----+

答案 3 :(得分:4)

你可以简单地使用这个

df1.join(df2, Seq("ts","id"),"TYPE-OF-JOIN")

这里的TYPE-OF-JOIN可以是

  • fullouter

例如,我有两个这样的数据帧:

// df1
word   count1
w1     10   
w2     15  
w3     20

// df2
word   count2
w1     100   
w2     150  
w5     200

如果你进行了fullouter join,那么结果就像这样

df1.join(df2, Seq("word"),"fullouter").show()

word   count1  count2
w1     10      100
w2     15      150
w3     20      null
w5     null    200

答案 4 :(得分:1)

这是SQL的正常行为,我正在为此做些什么:

  • 删除或重命名源列
  • 加入
  • 删除重命名的列(如果有)

我在这里取代"全名"柱:

Java中的一些代码:

this
    .sqlContext
    .read()
    .parquet(String.format("hdfs:///user/blablacar/data/year=%d/month=%d/day=%d", year, month, day))
    .drop("fullname")
    .registerTempTable("data_original");

this
    .sqlContext
    .read()
    .parquet(String.format("hdfs:///user/blablacar/data_v2/year=%d/month=%d/day=%d", year, month, day))
    .registerTempTable("data_v2");

 this
    .sqlContext
    .sql(etlQuery)
    .repartition(1)
    .write()
    .mode(SaveMode.Overwrite)
    .parquet(outputPath);

查询的位置是:

SELECT
    d.*,
   concat_ws('_', product_name, product_module, name) AS fullname
FROM
    {table_source} d
LEFT OUTER JOIN
    {table_updates} u ON u.id = d.id

这是你只能用Spark我可以做的事情(从列表中删除列),非常非常有帮助!

答案 5 :(得分:0)

尝试一下

val df_combined = df1.join(df2, df1("ts") === df2("ts") && df1("id") === df2("id")).drop(df2("ts")).drop(df2("id"))

答案 6 :(得分:0)

最佳做法是在连接两个DF之前使它们的名称不同,然后相应地删除它们。

df1.columns =[id, age, income]
df2.column=[id, age_group]

df1.join(df2, on=df1.id== df2.id,how='inner').write.saveAsTable('table_name')

在重复列出现错误时将返回错误

尝试以下方法,而不要尝试以下方法:

df1.join(df2.withColumnRenamed('id','id_2'), on=df1.id== df2.id_2,how='inner').drop('id_2')

答案 7 :(得分:0)

将多个表连接在一起后,如果遇到重复项,我将通过一个简单的函数运行它们以重命名DF中的列。或者,you could drop these duplicate columns too

如果Names是具有列['Id', 'Name', 'DateId', 'Description']的表,而Dates是具有列['Id', 'Date', 'Description']的表,则列IdDescription将加入后被复制。

Names = sparkSession.sql("SELECT * FROM Names")
Dates = sparkSession.sql("SELECT * FROM Dates")
NamesAndDates = Names.join(Dates, Names.DateId == Dates.Id, "inner")
NamesAndDates = deDupeDfCols(NamesAndDates, '_')
NamesAndDates.saveAsTable("...", format="parquet", mode="overwrite", path="...")

deDupeDfCols的定义为:

def deDupeDfCols(df, separator=''):
    newcols = []

    for col in df.columns:
        if col not in newcols:
            newcols.append(col)
        else:
            for i in range(2, 1000):
                if (col + separator + str(i)) not in newcols:
                    newcols.append(col + separator + str(i))
                    break

    return df.toDF(*newcols)

结果数据框将包含列['Id', 'Name', 'DateId', 'Description', 'Id2', 'Date', 'Description2']

抱歉,这个答案是用Python编写的-我对Scala并不熟悉,但这是我在Google上搜索此问题时所遇到的问题,并且我确定Scala代码不会太

答案 8 :(得分:0)

内部联接是spark中的默认联接,下面是它的简单语法。

leftDF.join(rightDF,"Common Col Nam")

对于其他联接,您可以遵循以下语法

leftDF.join(rightDF,Seq("Common Columns comma seperated","join type")

如果“名称”列不常见,则

leftDF.join(rightDF,leftDF.col("x")===rightDF.col("y),"join type")