web-dev-qa-db-fra.com

SparkSQL: Puis-je exploser deux variables différentes dans la même requête?

J'ai la requête d'éclatement suivante, qui fonctionne bien:

data1 = sqlContext.sql("select explode(names) as name from data")

Je veux faire exploser un autre champ "couleurs", donc la sortie finale pourrait être le produit cartésien des noms et des couleurs. J'ai donc fait:

data1 = sqlContext.sql("select explode(names) as name, explode(colors) as color from data")

Mais j'ai eu les erreurs:

 Only one generator allowed per select but Generate and and Explode found.;

Est-ce que quelqu'un a une idée?


Je peux réellement le faire fonctionner en faisant deux étapes:

   data1 = sqlContext.sql("select explode(names) as name from data")
   data1.registerTempTable('data1')
   data1 = sqlContext.sql("select explode(colors) as color from data1")

Mais je me demande s'il est possible de le faire en une seule étape? Merci beaucoup!

11
Edamame

La syntaxe correcte est

select name, color 
from data 
lateral view explode(names) exploded_names as name 
lateral view explode(colors) exploded_colors as color

La raison pour laquelle la réponse de Rashid n'a pas fonctionné est qu'elle n'a pas "nommé" la table générée par LATERAL VIEW.

Explication

Pensez-y de cette façon: LATERAL VIEW Fonctionne comme un JOIN implicite avec une table éphémère créée pour chaque ligne à partir du structs dans la collection en cours de "visualisation". Ainsi, la façon d'analyser la syntaxe est la suivante:

LATERAL VIEW table_generation_function(collection_column) table_name AS col1, ...

Plusieurs colonnes de sortie

Si vous utilisez une fonction de génération de table telle que posexplode(), vous avez toujours une table de sortie mais avec plusieurs colonnes de sortie:

LATERAL VIEW posexplode(orders) exploded_orders AS order_number, order

Imbrication

Vous pouvez également "imbriquer" LATERAL VIEW En éclatant à plusieurs reprises des collections imbriquées, par exemple,

LATERAL VIEW posexplode(orders) exploded_orders AS order_number, order
LATERAL VIEW posexplode(order.items) exploded_items AS item_number, item

Considérations sur les performances

Bien que nous soyons sur le sujet de LATERAL VIEW, Il est important de noter que son utilisation via SparkSQL est plus efficace que son utilisation via le DataFrame DSL, par exemple, myDF.explode(). La raison en est que SQL peut raisonner avec précision sur le schéma tandis que l'API DSL doit effectuer une conversion de type entre un type de langage et la ligne de trame de données. Ce que l'API DSL perd en termes de performances, cependant, il gagne en flexibilité car vous pouvez renvoyer n'importe quel type pris en charge à partir de explode, ce qui signifie que vous pouvez effectuer une transformation plus compliquée en une seule étape.

Mettre à jour

Dans les versions récentes de Spark, l'explosion au niveau ligne via df.explode() a été déconseillée au profit d'une explosion au niveau colonne via df.select(..., explode(...).as(...)). Il existe également une explode_outer(), qui produira des lignes de sortie même si l'entrée à exploser est null. L'explosion au niveau de la colonne ne souffre pas des problèmes de performances de l'explosion au niveau de la ligne mentionnés ci-dessus car Spark peut effectuer la transformation entièrement en utilisant des représentations de données de ligne internes.

26
Sim

Essayez plutôt une vue latérale exploser.

select name, color from data lateral view explode(names) as name lateral view explode(colors) as color;
2
Rashid Ali

Plus d'une explosion n'est pas autorisée dans spark sql car c'est trop déroutant. C'est parce que vous obtenez un produit cartésien implicite des deux choses que vous explosez. Si vous voulez en faire plus d'une exploser, vous devez utiliser plusieurs sélections. Hive a une vue latérale qui peut atteindre ce dont vous avez besoin (expliqué par Rashid ALi dans sa réponse ici). Je recommanderais personnellement deux sélections avec des trames de données car il est beaucoup plus efficace en étincelle. Supposons maintenant que "données" est un bloc de données.

val data1 = data.select($"id",$"names",$explode($"colors").alias("colors"))
           //select required columns from colors 
            .select($"id",$"colors.field1",explode($"names").alias("names"))
            //now select required cols from names
            .select($"id",$"field1",$"names.col1",$"names.col2")

Vous pouvez faire des sélections ci-dessus dans plusieurs trames de données ou dans une seule comme ci-dessus, cela ne fait aucune différence en termes de performances.

1
dheee

Il existe un moyen simple d'exploser sur plusieurs colonnes par df.withColumn.

scala> val data = spark.sparkContext.parallelize(Seq((Array("Alice", "Bob"), Array("Red", "Green", "Blue"))))
  .toDF("names", "colors")
data: org.Apache.spark.sql.DataFrame = [names: array<string>, colors: array<string>]

scala> data.show
+------------+------------------+                                               
|       names|            colors|
+------------+------------------+
|[Alice, Bob]|[Red, Green, Blue]|
+------------+------------------+

scala> data.withColumn("name", explode('names))
  .withColumn("color", explode('colors))
  .show

+------------+------------------+-----+-----+
|       names|            colors| name|color|
+------------+------------------+-----+-----+
|[Alice, Bob]|[Red, Green, Blue]|Alice|  Red|
|[Alice, Bob]|[Red, Green, Blue]|Alice|Green|
|[Alice, Bob]|[Red, Green, Blue]|Alice| Blue|
|[Alice, Bob]|[Red, Green, Blue]|  Bob|  Red|
|[Alice, Bob]|[Red, Green, Blue]|  Bob|Green|
|[Alice, Bob]|[Red, Green, Blue]|  Bob| Blue|
+------------+------------------+-----+-----+
0
Todd Leo