web-dev-qa-db-ja.com

Sparkでネストされたコレクションを読み取る方法

寄木細工のテーブルがあり、列の1つが

、array <struct <col1、col2、.. colN >>

LATERAL VIEW構文を使用して、Hiveのこのテーブルに対してクエリを実行できます。

このテーブルをRDDに読み込む方法、さらに重要なことに、このネストされたコレクションをSparkでフィルター処理、マップなどする方法は?

Sparkのドキュメントにこれに関する参照が見つかりませんでした。事前に情報をありがとうございます!

ps。テーブルにいくつかの統計を示すのに役立つかもしれないと感じました。メインテーブルの列数〜600。行数〜200m。ネストされたコレクションの「列」の数〜10。ネストされたコレクションの平均レコード数〜35。

18
Tagar

ネストされたコレクションの場合、魔法はありません。 Sparkは、RDD[(String, String)]およびRDD[(String, Seq[String])]と同じ方法で処理されます。

ただし、Parquetファイルからこのようなネストされたコレクションを読み取るのは難しい場合があります。

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

寄木細工のファイルを書きなさい:

_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")
_

寄木細工のファイルを読む:

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

重要な部分はrow.getAs[Seq[Row]](1)です。 structのネストされたシーケンスの内部表現は_ArrayBuffer[Row]_であり、_Seq[Row]_の代わりにそのスーパータイプを使用できます。 _1_は、外側の行の列インデックスです。ここではgetAsメソッドを使用しましたが、Sparkの最新バージョンには代替手段があります。 Row trait のソースコードを参照してください。

_RDD[Outer]_を取得したので、必要な変換またはアクションを適用できます。

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

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

寄木細工のファイルを読み取るためだけにSpark-SQLライブラリを使用したことに注意してください。たとえば、RDDにマッピングする前に、必要な列のみをDataFrameで直接選択できます。

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

私が使用しているのはPythonベースの答えです。 Scalaにも似たようなものがあると思います。

explode関数は、Spark 1.4.0に追加され、データフレーム内のネストされた配列を処理します Python API docs によると)。

テストデータフレームを作成します。

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

explodeを使用してリスト列をフラット化します。

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

別のアプローチは、次のようなパターンマッチングを使用することです。

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
})

Rowで直接パターンマッチングを実行できますが、いくつかの理由で失敗する可能性があります。

3

上記の回答はすべて素晴らしい回答であり、さまざまな側面からこの質問に取り組みます。 Spark SQLは、ネストされたデータにアクセスするための非常に便利な方法でもあります。

SQLでexplode()を直接使用してネストされたコレクションをクエリする方法の例を次に示します。

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

tsp_idsは構造体のネストであり、上記の外部クエリで選択しているperson_seq_noを含む多くの属性があります。

上記は、Spark 2.0でテストされました。小さなテストを行ったが、Spark 1.6で機能しません。この質問は、Spark 2は存在しなかったので、この回答はネストされた構造を処理するために利用可能なオプションのリストにうまく追加されます。

Spark 2.2はOUTER explodeもサポートしているため(例:ネストされた場合、LATERAL VIEW OUTERコレクションは空ですが、親レコードの属性が必要です):

SQLアクセスのexplode()で注目すべき未解決のJIRA:

1
Tagar