Spark - 从嵌套数组的结构中选择列时出现错误

6

=========== 更新 ========

我在我的 JSON(struct_c 和 array_d)中添加了更多细节,以便更清楚地了解我遇到异常的原因。

============================

您好,

我有一个具有嵌套结构数组的 Spark DataFrame。我想从该结构中选择一列,但出现了错误消息:“org.apache.spark.sql.AnalysisException: 由于数据类型不匹配,无法解析 'home.array_a.array_b['a']',第二个参数需要整数类型,但 'a' 的类型是字符串类型”。

以下是我的数据:

{
  "home": {
    "a_number": 5,
    "a_string": "six",
    "array_a": [
      {
        "array_b": [{"a": "1", "b": 2}],
        "struct_c": {"a": 1.1, "b": 1.3},
        "array_d": ["a", "b", "c"]
      },
      {
        "array_b": [{"a": "3", "b": 4}],
        "struct_c": {"a": 1.5, "b": 1.6},
        "array_d": ["x", "y", "z"]
      }
    ]
  }
}

以下是我的数据架构:

mydf1 = spark.read.option("multiline", "true").json("myJson.json")
mydf1.printSchema()

root
 |-- home: struct (nullable = true)
 |    |-- a_number: long (nullable = true)
 |    |-- a_string: string (nullable = true)
 |    |-- array_a: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- array_b: array (nullable = true)
 |    |    |    |    |-- element: struct (containsNull = true)
 |    |    |    |    |    |-- a: string (nullable = true)
 |    |    |    |    |    |-- b: long (nullable = true)
 |    |    |    |-- array_d: array (nullable = true)
 |    |    |    |    |-- element: string (containsNull = true)
 |    |    |    |-- struct_c: struct (nullable = true)
 |    |    |    |    |-- a: double (nullable = true)
 |    |    |    |    |-- b: double (nullable = true)

当我从数组array_a中选择struct_c或array_d(字符串数组)中的数据时,没有任何问题。
mydf1.select("home.array_a.array_d").show(10, False)

+----------------------+
|array_d               |
+----------------------+
|[[a, b, c], [x, y, z]]|
+----------------------+

mydf1.select(col("home.array_a.struct_c.a").alias("struct_field_inside_arrayA")).show(10, False)

+--------------------------+
|struct_field_inside_arrayA|
+--------------------------+
|[1.1, 1.5]                |
+--------------------------+

这就是失败的地方:

mydf1.select("home.array_a.array_b.a").printSchema()
mydf1.select("home.array_a.array_b.a").show()

我需要的是一个包含字符串的二维数组([["1", "3"]] 是我的样例JSON

你能帮忙解释一下为什么它失败了吗?

谢谢你的帮助。

执行第4行失败: mydf1.select("home.array_a.array_b.a").printSchema() 回溯信息(最近的调用在最上面): An error occurred while calling o15300.select.: org.apache.spark.sql.AnalysisException: 由于数据类型不匹配,无法解析 'home.array_a.array_b['a']':参数2需要整数类型,但 ''a'' 的类型为字符串。;; 'Project [home#18213.array_a.array_b[a] AS a#18217] +- Relation[home#18213] json at org.apache.spark.sql.catalyst.analysis.package$AnalysisErrorAt.failAnalysis(package.scala:42) at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1$$anonfun$apply$3.applyOrElse(CheckAnalysis.scala:115) at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1$$anonfun$apply$3.applyOrElse(CheckAnalysis.scala:107) at org.apache.spark.sql.catalyst.trees.TreeNode$$anonfun$transformUp$1.apply(TreeNode.scala:278) at org.apache.spark.sql.catalyst.trees.TreeNode$$anonfun$transformUp$1.apply(TreeNode.scala:278) at org.apache.spark.sql.catalyst.trees.CurrentOrigin$.withOrigin(TreeNode.scala:70) at org.apache.spark.sql.catalyst.trees.TreeNode.transformUp(TreeNode.scala:277) at org.apache.spark.sql.catalyst.trees.TreeNode$$anonfun$3.apply(TreeNode.scala:275) at org.apache.spark.sql.catalyst.trees.TreeNode$$anonfun$3.apply(TreeNode.scala:275) at org.apache.spark.sql.catalyst.trees.TreeNode$$anonfun$4.apply(TreeNode.scala:326) at org.apache.spark.sql.catalyst.trees.TreeNode.mapProductIterator(TreeNode.scala:187) at org.apache.spark.sql.catalyst.trees.TreeNode.mapChildren(TreeNode.scala:324) at org.apache.spark.sql.catalyst.trees.TreeNode.transformUp(TreeNode.scala:275) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$transformExpressionsUp$1.apply(QueryPlan.scala:93) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$transformExpressionsUp$1.apply(QueryPlan.scala:93) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$1.apply(QueryPlan.scala:105) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$1.apply(QueryPlan.scala:105) at org.apache.spark.sql.catalyst.trees.CurrentOrigin$.withOrigin(TreeNode.scala:70) at org.apache.spark.sql.catalyst.plans.QueryPlan.transformExpression$1(QueryPlan.scala:104) at org.apache.spark.sql.catalyst.plans.QueryPlan.org$apache$spark$sql$catalyst$plans$QueryPlan$$recursiveTransform$1(QueryPlan.scala:116) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$org$apache$spark$sql$catalyst$plans$QueryPlan$$recursiveTransform$1$2.apply(QueryPlan.scala:121) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234) at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59) at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48) at scala.collection.TraversableLike$class.map(TraversableLike.scala:234) at scala.collection.AbstractTraversable.map(Traversable.scala:104) at org.apache.spark.sql.catalyst.plans.QueryPlan.org$apache$spark$sql$catalyst$plans$QueryPlan$$recursiveTransform$1(QueryPlan.scala:121) at org.apache.spark.sql.catalyst.plans.QueryPlan$$anonfun$2.apply(QueryPlan.scala:126) at org.apache.spark.sql.catalyst.trees.TreeNode.mapProductIterator(TreeNode.scala:187) at org.apache.spark.sql.catalyst.plans.QueryPlan.mapExpressions(QueryPlan.scala:126) at org.apache.spark.sql.catalyst.plans.QueryPlan.transformExpressionsUp(QueryPlan.scala:93) at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1.apply(CheckAnalysis.scala:107) at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1.apply(CheckAnalysis.scala:85) at org.apache.spark.sql
4个回答

4
array_aarray_b 是数组类型,因此您不能直接选择它们的元素。 您需要按照下面的方式拆分它们,或者您可以按索引获取。
mydf1.withColumn("array_a", explode($"home.array_a"))
  .withColumn("array_b", explode($"array_a.array_b"))
  .select("array_b.a").show(false)

这将为您提供:
+---+
|a  |
+---+
|1  |
|3  |
+---+

谢谢Shankar。然而,“由于array_a和array_b是数组类型,因此您无法直接选择其元素” <<<这不是真的,在我的原始帖子中,可以选择“home.array_a.another_number”。我不想使用explode,因为我最终会有太多记录,其他列上的值会重复。在随后的聚合中,需要进行groupBy。 - Averell

3
由于您对 element_at() 函数没有问题,我假设您正在使用 spark 2.4+,那么您可以尝试使用 Spark SQL 内置函数:transform[1][2] + flatten
>>> mydf1.selectExpr('flatten(transform(home.array_a.array_b, x -> x.a)) as array_field_inside_array').show()
+------------------------+
|array_field_inside_array|
+------------------------+
|                  [1, 3]|
+------------------------+

我们使用 transform() 函数仅检索 home.array_a.array_b 的每个数组元素的字段 a 的值,并将它们转换为数组 [[1],[3]] 。然后将该数组展平为 [1, 3] 。如果您需要结果为 [[1, 3]] ,则只需添加array()函数即可。
array(flatten(transform(home.array_a.array_b, x -> x.a)))

谢谢@jxc。它有效。为了得到[[1,3]],我只需要删除“flatten”。在使用array(flatten(transform()))和仅使用transform()之间有区别吗?我找不到PySpark中的transform()等效项。它是否以其他名称的形式存在?谢谢。 - Averell
1
@Averell,只使用transform()函数,我们得到的是[[1], [3]],我认为这不是你想要的结果?这就是为什么我添加了flatten()函数。**transform()**是Spark SQL内置函数之一(https://spark.apache.org/docs/2.4.3/api/sql/index.html)。对于pyspark数据框,我们可以始终使用`df.selectExpr()`或`spark.sql.functions.expr()`来运行这些SQL函数 :),你可以搜索spark sql higher order functions以获取更多与数组操作相关的函数示例。 - jxc

0

你可以通过以下方式简单地进行选择:

spark.sql("SELECT home.array_a.array_b[0].a FROM <table>")

谢谢Lamanus。但这只会给出数组array_b的第一个元素。 - Averell
然后根据您的需要添加更多列。 - Lamanus
但我需要来自数组_b中所有元素的结构体字段“a”,而不是来自数组_b中第一个元素的所有结构体字段。 - Averell

0
在你的例子中,它失败了,因为你试图打印一个值的模式而不是列的模式。因此,如果你从选择语句中删除"a",那么你就可以打印所需的模式。
scala> dataDF.select("home.array_a.array_b").printSchema
root
 |-- array_b: array (nullable = true)
 |    |-- element: array (containsNull = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- a: string (nullable = true)
 |    |    |    |-- b: long (nullable = true)

如果你想从数组(array_b)中获取值,你需要给出索引。
scala> dataDF.select(element_at(col("home.array_a.array_b"),1)).show
+-----------------------------------+
|element_at(home.array_a.array_b, 1)|
+-----------------------------------+
|                           [[1, 2]]|
+-----------------------------------+

你能否提供预期的数据框架。


谢谢Praveen。但是这只会给出array_b的第一个元素,它是一个包含2个结构字段“a”和“b”的结构体。我需要的是从array_b的所有元素中获取结构字段“a”(在我的示例json中应该是[["1", "3"]])。 - Averell
你的输出是一个数据框,每个记录都是一个结构体/元组数组[(1, 2)]。我的期望是每个记录都是一个字符串数组的数组[["1", "3"]]。 - Averell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接