web-dev-qa-db-fra.com

Comment lire une collection imbriquée dans Spark

J'ai une table en parquet avec l'une des colonnes étant

, tableau <struct <col1, col2, .. colN >>

Peut exécuter des requêtes sur cette table dans Hive à l'aide de la syntaxe LATERAL VIEW.

Comment lire ce tableau dans un RDD, et surtout comment filtrer, mapper etc. cette collection imbriquée dans Spark?

Impossible de trouver des références à cela dans la documentation Spark. Merci d'avance pour toute information!

ps. J'ai pensé qu'il pourrait être utile de donner quelques statistiques sur la table. Nombre de colonnes dans le tableau principal ~ 600. Nombre de rangées ~ 200 m. Nombre de "colonnes" dans la collection imbriquée ~ 10. Nombre moyen d'enregistrements dans la collection imbriquée ~ 35.

18
Tagar

Il n'y a pas de magie dans le cas d'une collection imbriquée. Spark gérera de la même manière une RDD[(String, String)] et une RDD[(String, Seq[String])].

La lecture d'une telle collection imbriquée à partir de fichiers Parquet peut cependant être délicate.

Prenons un exemple du spark-Shell (1.3.1):

scala> import sqlContext.implicits._
import sqlContext.implicits._

scala> case class Inner(a: String, b: String)
defined class Inner

scala> case class Outer(key: String, inners: Seq[Inner])
defined class Outer

Écrivez le dossier parquet:

scala> val outers = sc.parallelize(List(Outer("k1", List(Inner("a", "b")))))
outers: org.Apache.spark.rdd.RDD[Outer] = ParallelCollectionRDD[0] at parallelize at <console>:25

scala> outers.toDF.saveAsParquetFile("outers.parquet")

Lire le dossier parquet:

scala> import org.Apache.spark.sql.catalyst.expressions.Row
import org.Apache.spark.sql.catalyst.expressions.Row

scala> val dataFrame = sqlContext.parquetFile("outers.parquet")
dataFrame: org.Apache.spark.sql.DataFrame = [key: string, inners: array<struct<a:string,b:string>>]   

scala> val outers = dataFrame.map { row =>
     |   val key = row.getString(0)
     |   val inners = row.getAs[Seq[Row]](1).map(r => Inner(r.getString(0), r.getString(1)))
     |   Outer(key, inners)
     | }
outers: org.Apache.spark.rdd.RDD[Outer] = MapPartitionsRDD[8] at map at DataFrame.scala:848

La partie importante est row.getAs[Seq[Row]](1). La représentation interne d'une séquence imbriquée de struct est ArrayBuffer[Row], Vous pouvez en utiliser n'importe quel super-type au lieu de Seq[Row]. Le 1 Est l'index de la colonne dans la ligne extérieure. J'ai utilisé la méthode getAs ici mais il existe des alternatives dans les dernières versions de Spark. Voir le code source du trait Row .

Maintenant que vous avez un RDD[Outer], Vous pouvez appliquer n'importe quelle transformation ou action souhaitée.

// Filter the outers
outers.filter(_.inners.nonEmpty)

// Filter the inners
outers.map(outer => outer.copy(inners = outer.inners.filter(_.a == "a")))

Notez que nous avons utilisé la bibliothèque spark-SQL uniquement pour lire le fichier parquet. Vous pouvez par exemple sélectionner uniquement les colonnes souhaitées directement sur le DataFrame, avant de le mapper à un RDD.

dataFrame.select('col1, 'col2).map { row => ... }
20
Lomig Mégard

Je vais donner une réponse basée sur Python puisque c'est ce que j'utilise. Je pense que Scala a quelque chose de similaire.

La fonction explode a été ajoutée dans Spark 1.4.0 pour gérer les tableaux imbriqués dans DataFrames, selon les documentation API Python .

Créez une trame de données de test:

from pyspark.sql import Row

df = sqlContext.createDataFrame([Row(a=1, intlist=[1,2,3]), Row(a=2, intlist=[4,5,6])])
df.show()

## +-+--------------------+
## |a|             intlist|
## +-+--------------------+
## |1|ArrayBuffer(1, 2, 3)|
## |2|ArrayBuffer(4, 5, 6)|
## +-+--------------------+

Utilisez explode pour aplatir la colonne de liste:

from pyspark.sql.functions import explode

df.select(df.a, explode(df.intlist)).show()

## +-+---+
## |a|_c0|
## +-+---+
## |1|  1|
## |1|  2|
## |1|  3|
## |2|  4|
## |2|  5|
## |2|  6|
## +-+---+
8
dnlbrky

Une autre approche consisterait à utiliser la correspondance de motifs comme celle-ci:

val rdd: RDD[(String, List[(String, String)]] = dataFrame.map(_.toSeq.toList match { 
  case List(key: String, inners: Seq[Row]) => key -> inners.map(_.toSeq.toList match {
    case List(a:String, b: String) => (a, b)
  }).toList
})

Vous pouvez faire correspondre les motifs directement sur Row, mais il est susceptible d'échouer pour plusieurs raisons.

3

Les réponses ci-dessus sont toutes d'excellentes réponses et abordent cette question de différents côtés; Spark SQL est également un moyen très utile d'accéder aux données imbriquées.

Voici un exemple d'utilisation directe de explode () dans SQL pour interroger une collection imbriquée.

SELECT hholdid, tsp.person_seq_no 
FROM (  SELECT hholdid, explode(tsp_ids) as tsp 
        FROM disc_mrt.unified_fact uf
     )

tsp_ids est un emboîtement de structures, qui possède de nombreux attributs, dont person_seq_no que je sélectionne dans la requête externe ci-dessus.

Ci-dessus a été testé dans Spark 2.0. J'ai fait un petit test et cela ne fonctionne pas dans Spark 1.6. Cette question a été posée quand Spark 2 n'était pas là, donc cette réponse s'ajoute bien à la liste des options disponibles pour gérer les structures imbriquées.

Jetez également un œil sur la manière compatible JIRA pour Hive d'interroger des données imbriquées à l'aide de LATERAL VIEW OUTER syntaxe, car Spark 2.2 prend également en charge OUTER explode (par exemple, lorsqu'une collection imbriquée est vide, mais que vous souhaitez toujours avoir des attributs d'un enregistrement parent):

JIRA non résolu notable sur explode () pour l'accès SQL:

1
Tagar