Kafkaからストリームを読み取っていて、値をKafka(JSON)からStructureに変換します。
from_json
にはタイプString
のスキーマをとるバリアントがありますが、サンプルが見つかりませんでした。以下のコードで何が問題なのかを教えてください。
エラー
Exception in thread "main" org.Apache.spark.sql.catalyst.parser.ParseException:
extraneous input '(' expecting {'SELECT', 'FROM', 'ADD', 'AS', 'ALL', 'DISTINCT',
== SQL ==
STRUCT ( `firstName`: STRING, `lastName`: STRING, `email`: STRING, `addresses`: ARRAY ( STRUCT ( `city`: STRING, `state`: STRING, `Zip`: STRING ) ) )
-------^^^
at org.Apache.spark.sql.catalyst.parser.ParseException.withCommand(ParseDriver.scala:217)
プログラム
public static void main(String[] args) throws AnalysisException {
String master = "local[*]";
String brokers = "quickstart:9092";
String topics = "simple_topic_6";
SparkSession sparkSession = SparkSession
.builder().appName(EmployeeSchemaLoader.class.getName())
.master(master).getOrCreate();
String employeeSchema = "STRUCT ( firstName: STRING, lastName: STRING, email: STRING, " +
"addresses: ARRAY ( STRUCT ( city: STRING, state: STRING, Zip: STRING ) ) ) ";
SparkContext context = sparkSession.sparkContext();
context.setLogLevel("ERROR");
SQLContext sqlCtx = sparkSession.sqlContext();
Dataset<Row> employeeDataset = sparkSession.readStream().
format("kafka").
option("kafka.bootstrap.servers", brokers)
.option("subscribe", topics).load();
employeeDataset.printSchema();
employeeDataset = employeeDataset.withColumn("strValue", employeeDataset.col("value").cast("string"));
employeeDataset = employeeDataset.withColumn("employeeRecord",
functions.from_json(employeeDataset.col("strValue"),employeeSchema, new HashMap<>()));
employeeDataset.printSchema();
employeeDataset.createOrReplaceTempView("employeeView");
sparkSession.catalog().listTables().show();
sqlCtx.sql("select * from employeeView").show();
}
あなたの質問は、String
ベースのスキーマを持つfrom_json
のバリアントがJavaでのみ利用可能であり、 最近 が_に追加されていることを見つけるのに役立ちましたSpark API for Scala次の2.3.0。私は長い間、Spark API for Scalaが常に最も機能が豊富であり、変更前はそうではなかったはずだということを学ぶのに役立ったという強い信念を持って生きてきました。 2.3.0で(!)
質問に戻ると、実際には文字列ベースのスキーマをJSONまたはDDL形式で定義できます。
JSONを手動で作成するのは少し面倒なので、別のアプローチを取ります(私がScala開発者であることを考えると、かなり簡単です)。
まず、ScalaのSparkAPIを使用してスキーマを定義しましょう。
import org.Apache.spark.sql.types._
val addressesSchema = new StructType()
.add($"city".string)
.add($"state".string)
.add($"Zip".string)
val schema = new StructType()
.add($"firstName".string)
.add($"lastName".string)
.add($"email".string)
.add($"addresses".array(addressesSchema))
scala> schema.printTreeString
root
|-- firstName: string (nullable = true)
|-- lastName: string (nullable = true)
|-- email: string (nullable = true)
|-- addresses: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- city: string (nullable = true)
| | |-- state: string (nullable = true)
| | |-- Zip: string (nullable = true)
それはあなたのスキーマと一致しているようですよね?
これにより、スキーマをJSONでエンコードされた文字列に変換するのは、json
メソッドで簡単に行えました。
val schemaAsJson = schema.json
schemaAsJson
はまさにあなたのJSON文字列で、かなり...うーん...複雑に見えます。表示の目的で、私はむしろprettyJson
メソッドを使用したいと思います。
scala> println(schema.prettyJson)
{
"type" : "struct",
"fields" : [ {
"name" : "firstName",
"type" : "string",
"nullable" : true,
"metadata" : { }
}, {
"name" : "lastName",
"type" : "string",
"nullable" : true,
"metadata" : { }
}, {
"name" : "email",
"type" : "string",
"nullable" : true,
"metadata" : { }
}, {
"name" : "addresses",
"type" : {
"type" : "array",
"elementType" : {
"type" : "struct",
"fields" : [ {
"name" : "city",
"type" : "string",
"nullable" : true,
"metadata" : { }
}, {
"name" : "state",
"type" : "string",
"nullable" : true,
"metadata" : { }
}, {
"name" : "Zip",
"type" : "string",
"nullable" : true,
"metadata" : { }
} ]
},
"containsNull" : true
},
"nullable" : true,
"metadata" : { }
} ]
}
これがJSONのスキーマです。
DataType
を使用して、JSON文字列を「検証」できます(Sparkがfrom_json
の内部で使用する DataType.fromJson を使用)。
import org.Apache.spark.sql.types.DataType
val dt = DataType.fromJson(schemaAsJson)
scala> println(dt.sql)
STRUCT<`firstName`: STRING, `lastName`: STRING, `email`: STRING, `addresses`: ARRAY<STRUCT<`city`: STRING, `state`: STRING, `Zip`: STRING>>>
すべて問題ないようです。サンプルデータセットでこれをチェックしているのでしょうか?
val rawJsons = Seq("""
{
"firstName" : "Jacek",
"lastName" : "Laskowski",
"email" : "[email protected]",
"addresses" : [
{
"city" : "Warsaw",
"state" : "N/A",
"Zip" : "02-791"
}
]
}
""").toDF("rawjson")
val people = rawJsons
.select(from_json($"rawjson", schemaAsJson, Map.empty[String, String]) as "json")
.select("json.*") // <-- flatten the struct field
.withColumn("address", explode($"addresses")) // <-- explode the array field
.drop("addresses") // <-- no longer needed
.select("firstName", "lastName", "email", "address.*") // <-- flatten the struct field
scala> people.show
+---------+---------+---------------+------+-----+------+
|firstName| lastName| email| city|state| Zip|
+---------+---------+---------------+------+-----+------+
| Jacek|Laskowski|[email protected]|Warsaw| N/A|02-791|
+---------+---------+---------------+------+-----+------+