web-dev-qa-db-fra.com

Qu'est-ce qui ne va pas avec `unionAll` de Spark` DataFrame`?

À l’aide de Spark 1.5.0 et du code suivant, j’attends de unionAll qu’elle union DataFrames en fonction de son nom de colonne. Dans le code, j'utilise de la FunSuite pour transmettre 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
  }
}

Sortie:

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

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

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

Pourquoi le résultat contient -il mélangé "b" et "a" colonnes, au lieu d'aligner les colonnes sur des noms de colonnes? Cela ressemble à un sérieux bug !?

18
Martin Senne

Cela ne ressemble pas du tout à un bug. Ce que vous voyez est un comportement SQL standard et tous les principaux RDMBS, y compris PostgreSQL , MySQL , Oracle et MS SQL se comportent exactement de la même manière. Vous trouverez des exemples SQL Fiddle liés à des noms.

Pour citer Manuel PostgreSQL :

Pour calculer l'union, l'intersection ou la différence de deux requêtes, celles-ci doivent être "compatibles avec l'union", ce qui signifie qu'elles renvoient le même nombre de colonnes et que les colonnes correspondantes ont des types de données compatibles.

Les noms de colonne, à l'exclusion de la première table de l'opération set, sont simplement ignorés.

Ce comportement vient directement de l'algèbre relationnelle où le bloc de construction de base est un tuple. Étant donné que les tuples sont commandés, l'union de deux jeux de tuples est équivalente (en ignorant la gestion des doublons) à la sortie que vous obtenez ici.

Si vous voulez utiliser des noms, vous pouvez faire quelque chose comme ça

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: _*))
}

Pour vérifier les noms et les types, cela devrait suffire à remplacer columns par:

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

Ce problème est en train d'être corrigé dans spark2.3. Ils ajoutent le support de unionByName dans le jeu de données. 

https://issues.Apache.org/jira/browse/SPARK-21043
3

pas de problèmes/bugs - si vous observez très attentivement votre classe d’affaires B, vous serez alors clair… vous avez mentionné la commande (b, a) ---> cela est prévu selon la commande

classe de cas A (a: Int, b: Int) classe de cas B (b: Int, a: Int)

merci, Subbu

1

Comme indiqué dans SPARK-9813 , il semble que, tant que les types de données et le nombre de colonnes sont identiques, l’opération unionAll devrait fonctionner. S'il vous plaît voir les commentaires pour une discussion supplémentaire.

0
Rohan Aletty