エラーが報告されました:
23/12/04 14:58:06 INFO ContextCleaner: Cleaned accumulator 12
Exception in thread "main" java.lang.UnsupportedOperationException: No Encoder found for org.apache.spark.sql.Row
- array element class: "org.apache.spark.sql.Row"
- field (class: "scala.collection.Seq", name: "_3")
- array element class: "scala.Tuple3"
- field (class: "scala.collection.Seq", name: "_2")
- root class: "scala.Tuple2"
at org.apache.spark.sql.catalyst.ScalaReflection$.$anonfun$serializerFor$1(ScalaReflection.scala:651)
問題の原因:
Spark SQL の Dataset
クラスでは、JVM オブジェクトを内部 Spark SQL 形式にシリアル化するためにエンコーダーが必要です。複雑な型 (Row
やカスタム クラスなど) の場合、Spark は暗黙的な Encoder
を必要とし、コードにはそのようなエンコーダーがないため、 UnsupportedOperationException
例外です。
付属コード
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.{Dataset, Row, SparkSession}
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
//https://blog.csdn.net/qq_52128187?type=blog
object Parent_child_V2 {
case class Node(title: String, key: String, children: Seq[Node])
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("ParentChildHierarchy").setMaster("local[1]")
val sc = new SparkContext(conf)
val spark = SparkSession.builder.appName("ParentChildHierarchy").getOrCreate()
import spark.implicits._
val df1 = sc.textFile("C:\\zzcode\\workplace\\src\\main\\data\\country")
val schema = StructType(
Array(
StructField("Country", StringType, nullable = true),
StructField("Gender", StringType, nullable = true),
StructField("Ethnicity", StringType, nullable = true),
StructField("Race", StringType, nullable = true)
)
)
val rowRDD = df1.map(line => {
val parts = line.split(",")
Row(parts(0), parts(1), parts(2), parts(3))
})
val df = spark.createDataFrame(rowRDD, schema)
df.show()
def toHierarchy(df: Dataset[Row]): Dataset[Node] = {
import spark.implicits._
// Generate the leaf nodes for Race under Ethnicity
val raceDF = df.groupBy("Country", "Gender", "Ethnicity")
.agg(collect_list($"Race").as("Races"))
.withColumn("Children", expr("transform(Races, race -> struct(race as title, concat(Country, '-', Gender, '-', Ethnicity, '-', race) as key, array() as children))"))
.select($"Country", $"Gender", $"Ethnicity", $"Children")
// Generate the child nodes for Ethnicity under Gender
val ethnicityDF = raceDF.groupBy("Country", "Gender")
.agg(collect_list(struct($"Ethnicity".as("title"), concat_ws("-", $"Country", $"Gender", $"Ethnicity").as("key"), $"Children".as("children"))).as("Children"))
.select($"Country", $"Gender", $"Children")
// Generate the top-level nodes for Gender under Country
val countryDF = ethnicityDF.groupBy("Country")
.agg(collect_list(struct($"Gender".as("title"), concat_ws("-", $"Country", $"Gender").as("key"), $"Children".as("children"))).as("Children"))
val nodeDS = countryDF.as[(String, Seq[(String, String, Seq[Row])])].map {
case (country, genderChildren) =>
val children = genderChildren.map { case (gender, key, childRows) =>
val childNodes = childRows.map { row =>
val title = row.getAs[String]("title")
val key = row.getAs[String]("key")
val grandChildren = row.getAs[Seq[Row]]("children").map { gcRow =>
Node(gcRow.getAs[String]("title"), gcRow.getAs[String]("key"), Seq.empty[Node])
}
Node(title, key, grandChildren)
}
Node(gender, s"$country-$gender", childNodes)
}
Node(country, country, children)
}
nodeDS
}
val resultDS = toHierarchy(df)
resultDS.show(false)
}
}
問題が解決しました:
- クラスのエンコーダとして
Encoders.kryo[Node]
します。 Kryo エンコーダーは、任意のオブジェクトを Spark SQL の内部形式にシリアル化するために使用されます。通常、ユーザー定義の複合型には Kryo エンコーダを使用することをお勧めします。 追加Node
toHierarchy
関数で???
タグを使用するには、特定のビジネス ロジックを入力して階層構造を作成する必要があります。- が を作成するときに SparkContext も初期化されるため、
SparkContext
の初期化と関連構成を削除SparkSession.builder
SparkSession
- エンコーダ
Encoders.kryo[Node]
は、ここでは例としてのみ使用されています。パフォーマンスの向上とシリアル化サイズの縮小が必要で、シリアル化の動作を明示的に制御できる場合は、カスタム タイプ (例: case クラスを使用し、Spark にエンコーダーを自動的に生成させる)。