如何按JSON格式过滤数组字段中的元素?

时间:2017-04-30 10:23:48

标签: apache-spark

我的数据集包含一个包含数组的字段,如下所示:

{ "name" : "James", "subjects" : [ "english", "french", "botany" ] },
{ "name" : "neo", "subjects" : [ "english", "physics" ] },
{ "name" : "john", "subjects" : [ "spanish", "mathematics" ] }

现在我想通过传递 Column 对象来使用 Dataset.filter 函数进行过滤。我尝试了 isin 函数和函数 array_contains 函数,但是没有用。

有没有办法创建 Column 对象来过滤数组,其中数组字段包含其中一个值?

3 个答案:

答案 0 :(得分:2)

有多种方法可以执行此操作 - 一旦您隐式导入Encoder

import sparkSession.implicits._

首先,您可以将DataFrame DataSet[Row]转换为强类型DataSet[Student],这样您就可以熟悉(至少如果您了解Scala)Scala成语:

case class Student(name: String, subjects: Seq[String])

sparkSession.read.json("my.json")
    .as[Student]
    .filter(_.subjects.contains("english"))

您还可以在Column中使用纯{ - 1}}方法,并使用有用的Spark DataFrame library中的array_contains

functions

最后,虽然它可能对您没有帮助,但请记住,您也可以使用同一sparkSession.read.json("my.json").filter(array_contains($"subjects", "english")) 库中的explode为每个主题添加自己的行:

functions

答案 1 :(得分:1)

Spark SQL的DataFrameReader支持所谓的JSON Lines文本格式(又名换行符分隔的JSON ),其中:

  

每一行都是有效的JSON值

您可以使用json运算符来读取数据集。

// on command line
$ cat subjects.jsonl
{ "name" : "James", "subjects" : [ "english", "french", "botany" ] }
{ "name" : "neo", "subjects" : [ "english", "physics" ] }
{ "name" : "john", "subjects" : [ "spanish", "mathematics" ] }

// in spark-shell
scala> val subjects = spark.read.json("subjects.jsonl")
subjects: org.apache.spark.sql.DataFrame = [name: string, subjects: array<string>]

scala> subjects.show(truncate = false)
+-----+-------------------------+
|name |subjects                 |
+-----+-------------------------+
|James|[english, french, botany]|
|neo  |[english, physics]       |
|john |[spanish, mathematics]   |
+-----+-------------------------+

scala> subjects.printSchema
root
 |-- name: string (nullable = true)
 |-- subjects: array (nullable = true)
 |    |-- element: string (containsNull = true)

有了这个,当你找到处理基于array的输入的收集功能时,你应该看一下函数libraryarray_containsexplode

你可以在@Vidya的答案中找到。

缺少的是我心爱的Dataset.flatMap,鉴于subjects数据集,可以按如下方式使用:

scala> subjects
  .as[(String, Seq[String])]  // convert to Dataset[(String, Seq[String])] for more type-safety
  .flatMap { case (student, subjects) => subjects.map(s => (student, s)) }  // typed expand
  .filter(_._2.toLowerCase == "english")  // filter out non-english subjects
  .show
+-----+-------+
|   _1|     _2|
+-----+-------+
|James|english|
|  neo|english|
+-----+-------+

然而,这并不像它的for-comprehension版本那样好/好。

val subjectsDF = subjects.as[(String, Seq[String])]
val englishStudents = for {
  (student, ss) <- subjectsDF  // flatMap
  subject <- ss                // map
  if subject.toLowerCase == "english"
} yield (student, subject)

scala> englishStudents.show
+-----+-------+
|   _1|     _2|
+-----+-------+
|James|english|
|  neo|english|
+-----+-------+

此外,从Spark 2.2 (即将发布)开始,您可以使用DataFrameReader.json运算符来阅读Dataset[String]

scala> spark.version
res0: String = 2.3.0-SNAPSHOT

import org.apache.spark.sql.Dataset
val subjects: Dataset[String] = Seq(
  """{ "name" : "James", "subjects" : [ "english", "french", "botany" ] }""",
  """{ "name" : "neo", "subjects" : [ "english", "physics" ] }""",
  """{ "name" : "john", "subjects" : [ "spanish", "mathematics" ]}""").toDS

scala> spark.read.option("inferSchema", true).json(subjects).show(truncate = false)
+-----+-------------------------+
|name |subjects                 |
+-----+-------------------------+
|James|[english, french, botany]|
|neo  |[english, physics]       |
|john |[spanish, mathematics]   |
+-----+-------------------------+

答案 2 :(得分:0)

据我了解,您正在尝试根据包含特定字符串的数组列在DataFrame中查找记录。例如,在这种情况下,您尝试查找包含特定主题的记录,例如“英语”。

首先创建一个示例数据框

import org.apache.spark.sql.functions._

val json_data = """[{ "name" : "James", "subjects" : [ "english", "french", "botany" ] },
{ "name" : "neo", "subjects" : [ "english", "physics" ] },
{ "name" : "john", "subjects" : [ "spanish", "mathematics" ] }]"""
val df = spark.read.json(Seq(json_data).toDS).toDF

现在,让我们尝试查找包含主题“ english”的记录。在这里,我们可以使用spark 2.4.0中提供的高阶函数“ array_contains”。

df.filter(array_contains($"subjects", "english")).show(truncate=false)

//输出

+-----+-------------------------+------------+
|name |subjects                 |contains_eng|
+-----+-------------------------+------------+
|James|[english, french, botany]|true        |
|neo  |[english, physics]       |true        |
+-----+-------------------------+------------+

您可以在此处(scalapython)找到有关这些功能的更多详细信息。

我希望这会有所帮助。