如何对数组列的元素进行切片和求和?

时间:2016-10-20 09:52:42

标签: scala apache-spark apache-spark-sql

我想使用SparkSQL在数组列上namespace app\common; class commonHelper { public function nameOfTheFunction1(arg.....) { //code...... } public function nameOfTheFunction2(arg.....) { //code...... } public function nameOfTheFunction3(arg.....) { //code...... } } /* in ur controller, models, ect... use like this */ use app\common\commonHelper; //use ur commonHelper in this file $common_helper = new commonHelper(); //make the object of commonHelper; $result = $common_heper->nameOfTheFunction1(parmas); //now call the function (或执行其他聚合函数)。

我有一张桌子

sum

我想对此+-------+-------+---------------------------------+ |dept_id|dept_nm| emp_details| +-------+-------+---------------------------------+ | 10|Finance| [100, 200, 300, 400, 500]| | 20| IT| [10, 20, 50, 100]| +-------+-------+---------------------------------+ 列的值进行求和。

预期查询:

emp_details

预期结果

sqlContext.sql("select sum(emp_details) from mytable").show

此外,我应该能够总结范围元素:

1500
180

结果

sqlContext.sql("select sum(slice(emp_details,0,3)) from mytable").show

当按预期对Array类型求和时,它表明sum期望参数为数字类型而不是数组类型。

我认为我们需要为此创建UDF。但怎么样?

我是否会面临使用UDF的任何性能点击? 除了UDF之外还有其他解决方案吗?

6 个答案:

答案 0 :(得分:9)

Spark 2.4.0

Spark 2.4开始,Spark SQL支持用于处理复杂数据结构的高阶函数,包括数组。

“现代”解决方案如下:

scala> input.show(false)
+-------+-------+-------------------------+
|dept_id|dept_nm|emp_details              |
+-------+-------+-------------------------+
|10     |Finance|[100, 200, 300, 400, 500]|
|20     |IT     |[10, 20, 50, 100]        |
+-------+-------+-------------------------+

input.createOrReplaceTempView("mytable")

val sqlText = "select dept_id, dept_nm, aggregate(emp_details, 0, (acc, value) -> acc + value) as sum from mytable"
scala> sql(sqlText).show
+-------+-------+----+
|dept_id|dept_nm| sum|
+-------+-------+----+
|     10|Finance|1500|
|     20|     IT| 180|
+-------+-------+----+

您可以在以下文章和视频中找到关于高阶函数的好读物:

  1. Introducing New Built-in and Higher-Order Functions for Complex Data Types in Apache Spark 2.4
  2. Working with Nested Data Using Higher Order Functions in SQL on Databricks
  3. An Introduction to Higher Order Functions in Spark SQL with Herman van Hovell (Databricks)
  4. Spark 2.3.2及更早版本

    免责声明由于Spark SQL执行Dataset.map的反序列化,我不推荐这种方法(即使它得到了最多的赞成)。该查询强制Spark反序列化数据并将其加载到JVM(从JVM外部的Spark管理的内存区域)。这将不可避免地导致更频繁的GC,从而使性能变差。

    一种解决方案是使用Dataset解决方案,其中Spark SQL和Scala的组合可以显示其功能。

    scala> val inventory = Seq(
         |   (10, "Finance", Seq(100, 200, 300, 400, 500)),
         |   (20, "IT", Seq(10, 20, 50, 100))).toDF("dept_id", "dept_nm", "emp_details")
    inventory: org.apache.spark.sql.DataFrame = [dept_id: int, dept_nm: string ... 1 more field]
    
    // I'm too lazy today for a case class
    scala> inventory.as[(Long, String, Seq[Int])].
      map { case (deptId, deptName, details) => (deptId, deptName, details.sum) }.
      toDF("dept_id", "dept_nm", "sum").
      show
    +-------+-------+----+
    |dept_id|dept_nm| sum|
    +-------+-------+----+
    |     10|Finance|1500|
    |     20|     IT| 180|
    +-------+-------+----+
    

    我将切片部分作为练习,因为它同样简单。

答案 1 :(得分:4)

explode()列上使用Array并因此通过唯一键聚合输出的可能方法。例如:

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

(mytable
  .withColumn("emp_sum",
    explode($"emp_details"))
  .groupBy("dept_nm")
  .agg(sum("emp_sum")).show)
+-------+------------+
|dept_nm|sum(emp_sum)|
+-------+------------+
|Finance|        1500|
|     IT|         180|
+-------+------------+

要仅选择数组中的特定值,我们可以使用链接问题的答案并稍加修改应用它:

val slice = udf((array : Seq[Int], from : Int, to : Int) => array.slice(from,to))

(mytable
  .withColumn("slice", 
    slice($"emp_details", 
      lit(0), 
      lit(3)))
  .withColumn("emp_sum",
    explode($"slice"))
  .groupBy("dept_nm")
  .agg(sum("emp_sum")).show)
+-------+------------+
|dept_nm|sum(emp_sum)|
+-------+------------+
|Finance|         600|
|     IT|          80|
+-------+------------+

数据

val data = Seq((10, "Finance", Array(100,200,300,400,500)),
               (20, "IT", Array(10,20,50,100)))
val mytable = sc.parallelize(data).toDF("dept_id", "dept_nm","emp_details")

答案 2 :(得分:4)

以下是mtoto's answer的替代方法而不使用groupBy(我真的不知道哪一个最快:UDF,mtoto解决方案或我的,欢迎评论)

一般来说,使用UDF会对性能产生影响。你可能想要阅读answer这个resource是对UDF的好读。

现在针对您的问题,您可以避免使用UDF。我将使用的是使用Scala逻辑生成的Column表达式。

数据:

val df = Seq((10, "Finance", Array(100,200,300,400,500)),
                  (20, "IT", Array(10,  20, 50,100)))
          .toDF("dept_id", "dept_nm","emp_details")

您需要一些技巧才能遍历ArrayType,您可以使用解决方案来发现各种问题(请参阅slice部分底部的编辑)。这是我的建议,但你可能会发现更好。首先,你采取最大长度

val maxLength = df.select(size('emp_details).as("l")).groupBy().max("l").first.getInt(0)

然后你使用它,测试你有一个较短的阵列

val sumArray = (1 until maxLength)
      .map(i => when(size('emp_details) > i,'emp_details(i)).otherwise(lit(0)))
      .reduce(_ + _)
      .as("sumArray")

val res = df
  .select('dept_id,'dept_nm,'emp_details,sumArray)

结果:

+-------+-------+--------------------+--------+
|dept_id|dept_nm|         emp_details|sumArray|
+-------+-------+--------------------+--------+
|     10|Finance|[100, 200, 300, 4...|    1500|
|     20|     IT|   [10, 20, 50, 100]|     180|
+-------+-------+--------------------+--------+

我建议你看看sumArray,了解它在做什么。

编辑:当然我只会再读一半问题...但是如果你想改变要求总和的项目,你可以看到这个解决方案变得明显(即你不需要)一个切片函数),只需用您需要的索引范围更改(0 until maxLength)

def sumArray(from: Int, max: Int) = (from until max)
      .map(i => when(size('emp_details) > i,'emp_details(i)).otherwise(lit(0)))
      .reduce(_ + _)
      .as("sumArray")

答案 3 :(得分:3)

从Spark 2.4开始,您可以使用slice函数进行切片:

import org.apache.spark.sql.functions.slice

val df = Seq(
  (10, "Finance", Seq(100, 200, 300, 400, 500)),
  (20, "IT", Seq(10, 20, 50, 100))
).toDF("dept_id", "dept_nm", "emp_details")

val dfSliced = df.withColumn(
   "emp_details_sliced",
   slice($"emp_details", 1, 3)
)

dfSliced.show(false)
+-------+-------+-------------------------+------------------+
|dept_id|dept_nm|emp_details              |emp_details_sliced|
+-------+-------+-------------------------+------------------+
|10     |Finance|[100, 200, 300, 400, 500]|[100, 200, 300]   |
|20     |IT     |[10, 20, 50, 100]        |[10, 20, 50]      |
+-------+-------+-------------------------+------------------+

并用aggregate对数组求和:

dfSliced.selectExpr(
  "*", 
  "aggregate(emp_details, 0, (x, y) -> x + y) as details_sum",  
  "aggregate(emp_details_sliced, 0, (x, y) -> x + y) as details_sliced_sum"
).show
+-------+-------+--------------------+------------------+-----------+------------------+
|dept_id|dept_nm|         emp_details|emp_details_sliced|details_sum|details_sliced_sum|
+-------+-------+--------------------+------------------+-----------+------------------+
|     10|Finance|[100, 200, 300, 4...|   [100, 200, 300]|       1500|               600|
|     20|     IT|   [10, 20, 50, 100]|      [10, 20, 50]|        180|                80|
+-------+-------+--------------------+------------------+-----------+------------------+

答案 4 :(得分:0)

缺少rdd方式,所以让我添加它。

val df = Seq((10, "Finance", Array(100,200,300,400,500)),(20, "IT", Array(10,20,50,100))).toDF("dept_id", "dept_nm","emp_details")

import scala.collection.mutable._

val rdd1 = df.rdd.map( x=> {val p = x.getAs[mutable.WrappedArray[Int]]("emp_details").toArray; Row.merge(x,Row(p.sum,p.slice(0,2).sum)) })

spark.createDataFrame(rdd1,df.schema.add(StructField("sumArray",IntegerType)).add(StructField("sliceArray",IntegerType))).show(false)

输出:

+-------+-------+-------------------------+--------+----------+
|dept_id|dept_nm|emp_details              |sumArray|sliceArray|
+-------+-------+-------------------------+--------+----------+
|10     |Finance|[100, 200, 300, 400, 500]|1500    |300       |
|20     |IT     |[10, 20, 50, 100]        |180     |30        |
+-------+-------+-------------------------+--------+----------+

答案 5 :(得分:0)

以zero323的出色答案为基础;如果您有一个长整数数组,即BIGINT,则需要按照第一段here中的说明将初始值从0更改为BIGINT(0)。 所以你有

dfSliced.selectExpr(
  "*", 
  "aggregate(emp_details, BIGINT(0), (x, y) -> x + y) as details_sum",  
  "aggregate(emp_details_sliced, BIGINT(0), (x, y) -> x + y) as details_sliced_sum"
).show