使用Spark`DataFrame`的`unionAll`出了什么问题?

时间:2015-09-21 21:49:33

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

使用Spark 1.5.0并给出以下代码,我希望unionAll基于其列名联合DataFrame。在代码中,我使用一些FunSuite传递SparkContext sc

object Entities {

  case class A (a: Int, b: Int)
  case class B (b: Int, a: Int)

  val as = Seq(
    A(1,3),
    A(2,4)
  )

  val bs = Seq(
    B(5,3),
    B(6,4)
  )
}

class UnsortedTestSuite extends SparkFunSuite {

  configuredUnitTest("The truth test.") { sc =>
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._
    val aDF = sc.parallelize(Entities.as, 4).toDF
    val bDF = sc.parallelize(Entities.bs, 4).toDF
    aDF.show()
    bDF.show()
    aDF.unionAll(bDF).show
  }
}

输出:

+---+---+
|  a|  b|
+---+---+
|  1|  3|
|  2|  4|
+---+---+

+---+---+
|  b|  a|
+---+---+
|  5|  3|
|  6|  4|
+---+---+

+---+---+
|  a|  b|
+---+---+
|  1|  3|
|  2|  4|
|  5|  3|
|  6|  4|
+---+---+

为什么结果包含混合" b"和"" 列,而不是根据列名对齐列?听起来像一个严重的错误!?

5 个答案:

答案 0 :(得分:36)

它根本不像一个bug。您看到的是标准SQL行为,并且每个主要RDMBS(包括PostgreSQLMySQLOracleMS SQL的行为完全相同。您将找到与名称链接的SQL Fiddle示例。

引用PostgreSQL manual

  

为了计算两个查询的并集,交集或差异,这两个查询必须是" union compatible",这意味着它们返回相同数量的列,并且相应的列具有兼容的数据类型

列名称(不包括set操作中的第一个表)将被忽略。

此行为直接来自关系代数,其中基本构建块是元组。由于元组是有序的,因此两组元组的并集对于你在这里得到的输出是等价的(忽略重复处理)。

如果您想使用名称进行匹配,可以执行以下操作

import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.col

def unionByName(a: DataFrame, b: DataFrame): DataFrame = {
  val columns = a.columns.toSet.intersect(b.columns.toSet).map(col).toSeq
  a.select(columns: _*).unionAll(b.select(columns: _*))
}

要检查名称和类型,应该足以将columns替换为:

a.dtypes.toSet.intersect(b.dtypes.toSet).map{case (c, _) => col(c)}.toSeq

答案 1 :(得分:3)

此问题已在spark2.3中修复。他们在数据集中添加了unionByName的支持。

https://issues.apache.org/jira/browse/SPARK-21043

答案 2 :(得分:1)

没有问题/错误-如果您非常仔细地观察案例B,那么您会很清楚。 案例类A->您提到了顺序(a,b),并且 案例类B->您已经提到了订单(b,a)--->根据订单的预期

案例类A(a:整数,b:整数)   案例B(b:Int,a:Int)

谢谢, 子

答案 3 :(得分:0)

正如SPARK-9813中所讨论的那样,只要数据类型和列数在各帧之间相同,unionAll操作就应该起作用。请参阅评论以获得更多讨论。

答案 4 :(得分:0)

使用unionByName:

文档摘录:

def unionByName(other: Dataset[T]): Dataset[T]

  

此函数与并集之间的区别在于此函数按名称(而不是按位置)解析列:

val df1 = Seq((1, 2, 3)).toDF("col0", "col1", "col2")
val df2 = Seq((4, 5, 6)).toDF("col1", "col2", "col0")
df1.union(df2).show

// output:
// +----+----+----+
// |col0|col1|col2|
// +----+----+----+
// |   1|   2|   3|
// |   4|   5|   6|
// +----+----+----+