web-dev-qa-db-fra.com

Comment éviter les colonnes en double après la jointure?

J'ai deux dataframes avec les colonnes suivantes:

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

et

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

Après je

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

Je me retrouve avec les colonnes suivantes: Array(ts, id, X1, X2, ts, id, Y1, Y2). Je pouvais m'attendre à ce que les colonnes communes soient supprimées. Y at-il quelque chose de plus qui doit être fait?

38
Neel

La réponse simple (à partir de Databricks FAQ à ce sujet )] consiste à effectuer la jointure dans laquelle les colonnes jointes sont exprimées sous la forme tableau de chaînes (ou une chaîne) au lieu d'un prédicat.

Vous trouverez ci-dessous un exemple adapté de Databricks FAQ mais avec deux colonnes de jointure afin de répondre à la question de l’affiche originale.

Voici le dataframe à gauche :

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|
+---------+--------+----------+--------+
*/

Voici le dataframe right :

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

right.show()

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

Voici une solution incorrecte , dans laquelle les colonnes de jointure sont définies en tant que prédicat left("firstname")===right("firstname") && left("lastname")===right("lastname").

Le résultat incorrect est que les colonnes firstname et lastname sont dupliquées dans le cadre de données joint:

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|
+---------+--------+----------+--------+---------+--------+------+
*/

La solution correcte consiste à définir les colonnes de jointure comme un tableau de chaînes Seq("firstname", "lastname"). Le cadre de données en sortie n'a pas de colonnes dupliquées:

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|
+---------+--------+----------+--------+------+
*/
29

C'est un comportement attendu. DataFrame.join la méthode est équivalente à une jointure SQL comme ceci

SELECT * FROM a JOIN b ON joinExprs

Si vous souhaitez ignorer les colonnes en double, supprimez-les ou sélectionnez-les par la suite. Si vous voulez lever l’ambiguïté, vous pouvez utiliser ces accès en utilisant le parent 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"))

ou utilisez des alias:

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

Pour les équi-jointures, il existe une syntaxe de raccourci spéciale qui prend soit ne séquence de chaînes :

val usingColumns: Seq[String] = ???

a.join(b, usingColumns)

ou comme chaîne simple

val usingColumn: String = ???

a.join(b, usingColumn)

qui ne conservent qu'une seule copie des colonnes utilisées dans une condition de jointure.

22
zero323

Cela fait un moment que je suis coincé avec cela, et ce n’est que récemment que j’ai trouvé une solution assez simple.

Dis a est

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|
+---+----+

et je peux le faire pour sélectionner uniquement la valeur dans dataframe a:

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

Vous pouvez simplement utiliser ceci

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

Ici, TYPE-OF-JOIN peut être

  • la gauche
  • droite
  • interne
  • fullouter

Par exemple, j'ai deux dataframes comme ceci:

// df1
Word   count1
w1     10   
w2     15  
w3     20

// df2
Word   count2
w1     100   
w2     150  
w5     200

Si vous faites une connexion complète, alors le résultat ressemble à ceci

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

Word   count1  count2
w1     10      100
w2     15      150
w3     20      null
w5     null    200
4
Abu Shoeb

Ceci est un comportement normal de SQL, ce que je fais pour cela:

  • Supprimer ou renommer les colonnes source
  • Faire la jointure
  • Supprimer la colonne renommée, le cas échéant

Ici, je remplace la colonne "nom complet":

Quelques codes en 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);

Où la requête est:

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

C’est quelque chose que vous ne pouvez faire qu’avec Spark je crois (colonne de la liste)), très très utile!

2
Thomas Decaux

essaye ça,

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