web-dev-qa-db-fra.com

Comment interroger la colonne de données JSON en utilisant Spark DataFrames?

J'ai une table Cassandra) qui, pour simplifier, ressemble à quelque chose comme:

key: text
jsonData: text
blobData: blob

Je peux créer un cadre de données de base pour cela en utilisant spark) et le connecteur spark-cassandra en utilisant:

val df = sqlContext.read
  .format("org.Apache.spark.sql.cassandra")
  .options(Map("table" -> "mytable", "keyspace" -> "ks1"))
  .load()

J'ai du mal à développer les données JSON dans la structure sous-jacente. Je souhaite enfin pouvoir filtrer en fonction des attributs de la chaîne json et renvoyer les données blob. Quelque chose comme jsonData.foo = "bar" et retourne blobData. Est-ce actuellement possible?

34
JDesuv

Spark> = 2.4

Si nécessaire, le schéma peut être déterminé en utilisant schema_of_json fonction (veuillez noter que cela suppose qu'une ligne arbitraire est un représentant valide du schéma).

import org.Apache.spark.sql.functions.{lit, schema_of_json}

val schema = schema_of_json(lit(df.select($"jsonData").as[String].first))
df.withColumn("jsonData", from_json($"jsonData", schema, Map[String, String]()))

Spark> = 2.1

Vous pouvez utiliser from_json fonction:

import org.Apache.spark.sql.functions.from_json
import org.Apache.spark.sql.types._

val schema = StructType(Seq(
  StructField("k", StringType, true), StructField("v", DoubleType, true)
))

df.withColumn("jsonData", from_json($"jsonData", schema))

Spark> = 1.6

Vous pouvez utiliser get_json_object qui prend une colonne et un chemin:

import org.Apache.spark.sql.functions.get_json_object

val exprs = Seq("k", "v").map(
  c => get_json_object($"jsonData", s"$$.$c").alias(c))

df.select($"*" +: exprs: _*)

et extrait des champs de chaînes individuelles qui peuvent ensuite être converties en types attendus.

L'argument path est exprimé à l'aide de la syntaxe à points, avec $. dénote la racine du document (puisque le code ci-dessus utilise l’interpolation de chaîne $ _ doit être échappé, donc $$.).

Spark <= 1.5 :

Est-ce actuellement possible?

Pour autant que je sache, ce n'est pas directement possible. Vous pouvez essayer quelque chose de semblable à ceci:

val df = sc.parallelize(Seq(
  ("1", """{"k": "foo", "v": 1.0}""", "some_other_field_1"),
  ("2", """{"k": "bar", "v": 3.0}""", "some_other_field_2")
)).toDF("key", "jsonData", "blobData")

Je suppose que le champ blob ne peut pas être représenté en JSON. Sinon, vous pouvez omettre de scinder et de rejoindre:

import org.Apache.spark.sql.Row

val blobs = df.drop("jsonData").withColumnRenamed("key", "bkey")
val jsons = sqlContext.read.json(df.drop("blobData").map{
  case Row(key: String, json: String) =>
    s"""{"key": "$key", "jsonData": $json}"""
}) 

val parsed = jsons.join(blobs, $"key" === $"bkey").drop("bkey")
parsed.printSchema

// root
//  |-- jsonData: struct (nullable = true)
//  |    |-- k: string (nullable = true)
//  |    |-- v: double (nullable = true)
//  |-- key: long (nullable = true)
//  |-- blobData: string (nullable = true)

Une approche alternative (moins chère, bien que plus complexe) consiste à utiliser un fichier UDF pour analyser le code JSON et générer une colonne struct ou map. Par exemple, quelque chose comme ceci:

import net.liftweb.json.parse

case class KV(k: String, v: Int)

val parseJson = udf((s: String) => {
  implicit val formats = net.liftweb.json.DefaultFormats
  parse(s).extract[KV]
})

val parsed = df.withColumn("parsedJSON", parseJson($"jsonData"))
parsed.show

// +---+--------------------+------------------+----------+
// |key|            jsonData|          blobData|parsedJSON|
// +---+--------------------+------------------+----------+
// |  1|{"k": "foo", "v":...|some_other_field_1|   [foo,1]|
// |  2|{"k": "bar", "v":...|some_other_field_2|   [bar,3]|
// +---+--------------------+------------------+----------+

parsed.printSchema

// root
//  |-- key: string (nullable = true)
//  |-- jsonData: string (nullable = true)
//  |-- blobData: string (nullable = true)
//  |-- parsedJSON: struct (nullable = true)
//  |    |-- k: string (nullable = true)
//  |    |-- v: integer (nullable = false)
60
zero323

Le from_json fonction est exactement ce que vous recherchez. Votre code ressemblera à quelque chose comme:

val df = sqlContext.read
  .format("org.Apache.spark.sql.cassandra")
  .options(Map("table" -> "mytable", "keyspace" -> "ks1"))
  .load()

//You can define whatever struct type that your json states
val schema = StructType(Seq(
  StructField("key", StringType, true), 
  StructField("value", DoubleType, true)
))

df.withColumn("jsonData", from_json(col("jsonData"), schema))
2
Sohum Sachdev

la chaîne JSON sous-jacente est

"{ \"column_name1\":\"value1\",\"column_name2\":\"value2\",\"column_name3\":\"value3\",\"column_name5\":\"value5\"}";

Vous trouverez ci-dessous le script permettant de filtrer le JSON et de charger les données requises dans Cassandra.

  sqlContext.read.json(rdd).select("column_name1 or fields name in Json", "column_name2","column_name2")
            .write.format("org.Apache.spark.sql.cassandra")
            .options(Map("table" -> "Table_name", "keyspace" -> "Key_Space_name"))
            .mode(SaveMode.Append)
            .save()
1
Suresh Chaganti

J'utilise le suivant

(disponible depuis la version 2.2.0, et je suppose que votre colonne de chaîne json est à l'index de colonne 0)

def parse(df: DataFrame, spark: SparkSession): DataFrame = {
    val stringDf = df.map((value: Row) => value.getString(0), Encoders.STRING)
    spark.read.json(stringDf)
}

Il en déduira automatiquement le schéma dans votre JSON. Documenté ici: https://spark.Apache.org/docs/2.3.0/api/Java/org/Apache/spark/sql/DataFrameReader.html

0
Falco Winkler