我想使用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之外还有其他解决方案吗?
答案 0 :(得分:9)
从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|
+-------+-------+----+
您可以在以下文章和视频中找到关于高阶函数的好读物:
免责声明由于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