Apache spark Spark SQL取代MySQL';s-群凝聚函数

Apache spark Spark SQL取代MySQL';s-群凝聚函数,apache-spark,aggregate-functions,apache-spark-sql,Apache Spark,Aggregate Functions,Apache Spark Sql,我有一个由两个字符串类型列组成的表(username,friend),对于每个用户名,我希望将其所有的friends收集在一行中,作为字符串连接起来。例如:('username1','friends1,friends2,friends3') 我知道MySQL是通过GROUP\u CONCAT实现的。使用Spark SQL有什么方法可以做到这一点吗?在继续之前:此操作是另一个groupByKey。虽然它有多个合法的应用程序,但它相对昂贵,因此请确保仅在需要时使用它 不是很简洁或高效的解决方案,但

我有一个由两个字符串类型列组成的表
(username,friend)
,对于每个用户名,我希望将其所有的friends收集在一行中,作为字符串连接起来。例如:
('username1','friends1,friends2,friends3')


我知道MySQL是通过
GROUP\u CONCAT
实现的。使用Spark SQL有什么方法可以做到这一点吗?

在继续之前:此操作是另一个
groupByKey
。虽然它有多个合法的应用程序,但它相对昂贵,因此请确保仅在需要时使用它


不是很简洁或高效的解决方案,但您可以使用Spark 1.5.0中引入的
UserDefinedAggregateFunction

objectgroupconcat扩展了UserDefinedAggregateFunction{
def inputSchema=new StructType()。添加(“x”,StringType)
def bufferSchema=new StructType().add(“buff”,ArrayType(StringType))
def数据类型=StringType
def deterministic=true
def初始化(缓冲区:可变聚合缓冲区)={
buffer.update(0,ArrayBuffer.empty[字符串])
}
def更新(缓冲区:可变聚合缓冲区,输入:行)={
如果(!input.isNullAt(0))
buffer.update(0,buffer.getSeq[String](0):+input.getString(0))
}
def合并(buffer1:MutableAggregationBuffer,buffer2:Row)={
buffer1.update(0,buffer1.getSeq[String](0)+buffer2.getSeq[String](0))
}
def求值(缓冲区:行)=UTF8String.fromString(
buffer.getSeq[String](0).mkString(“,”)
}
用法示例:

val df=sc.parallelize(Seq(
(“用户名1”、“朋友1”),
(“用户名1”、“好友2”),
(“用户名2”、“朋友1”),
(“用户名2”,“朋友3”)
)).toDF(“用户名”、“朋友”)
df.groupBy($“username”).agg(GroupConcat($“friend”)).show
## +---------+---------------+
##|用户名|朋友|
## +---------+---------------+
##|用户名1 |朋友1,朋友2|
##|用户名2 |朋友1,朋友3|
## +---------+---------------+
还可以创建Python包装器,如中所示

实际上,提取RDD、
groupByKey
mkString
和重建数据帧可以更快

通过将
collect\u list
函数(Spark>=1.6.0)与
concat\u ws
相结合,可以获得类似的效果:

import org.apache.spark.sql.functions.{collect_list,udf,lit}
df.groupBy($“用户名”)
.agg(concat_ws(“,”,collect_list($“friend”))。别名(“friends”))

使用pyspark<1.6执行此操作的一种方法,不幸的是,pyspark<1.6不支持用户定义的聚合函数:

byUsername = df.rdd.reduceByKey(lambda x, y: x + ", " + y)
如果要使其再次成为数据帧:

sqlContext.createDataFrame(byUsername, ["username", "friends"])
从1.6开始,您可以使用并加入创建的列表:

from pyspark.sql import functions as F
from pyspark.sql.types import StringType
join_ = F.udf(lambda x: ", ".join(x), StringType())
df.groupBy("username").agg(join_(F.collect_list("friend").alias("friends"))

您可以尝试collect_list函数

sqlContext.sql("select A, collect_list(B), collect_list(C) from Table1 group by A
或者你可以注册一个UDF,比如

sqlContext.udf.register("myzip",(a:Long,b:Long)=>(a+","+b))
您可以在查询中使用此函数

sqlConttext.sql("select A,collect_list(myzip(B,C)) from tbl group by A")

语言:Scala 火花版本:1.5.2

我也遇到了同样的问题,并尝试使用
udfs
解决它,但不幸的是,由于类型不一致,这导致代码中出现了更多问题。我能够解决这个问题,首先将
DF
转换为
RDD
,然后通过分组并以所需方式操作数据,然后将
RDD
转换回
DF
,如下所示:

val df = sc
     .parallelize(Seq(
        ("username1", "friend1"),
        ("username1", "friend2"),
        ("username2", "friend1"),
        ("username2", "friend3")))
     .toDF("username", "friend")

+---------+-------+
| username| friend|
+---------+-------+
|username1|friend1|
|username1|friend2|
|username2|friend1|
|username2|friend3|
+---------+-------+

val dfGRPD = df.map(Row => (Row(0), Row(1)))
     .groupByKey()
     .map{ case(username:String, groupOfFriends:Iterable[String]) => (username, groupOfFriends.mkString(","))}
     .toDF("username", "groupOfFriends")

+---------+---------------+
| username| groupOfFriends|
+---------+---------------+
|username1|friend2,friend1|
|username2|friend3,friend1|
+---------+---------------+

以下是您可以在PySpark中使用的函数:

import pyspark.sql.functions as F

def group_concat(col, distinct=False, sep=','):
    if distinct:
        collect = F.collect_set(col.cast(StringType()))
    else:
        collect = F.collect_list(col.cast(StringType()))
    return F.concat_ws(sep, collect)


table.groupby('username').agg(F.group_concat('friends').alias('friends'))
在SQL中:

select username, concat_ws(',', collect_list(friends)) as friends
from table
group by username

下面是实现group_concat功能的基于python的代码

输入数据:

顾客不,顾客汽车

1、丰田

2、宝马

1、奥迪

2、现代

from pyspark.sql import SparkSession
from pyspark.sql.types import StringType
from pyspark.sql.functions import udf
import pyspark.sql.functions as F

spark = SparkSession.builder.master('yarn').getOrCreate()

# Udf to join all list elements with "|"
def combine_cars(car_list,sep='|'):
  collect = sep.join(car_list)
  return collect

test_udf = udf(combine_cars,StringType())
car_list_per_customer.groupBy("Cust_No").agg(F.collect_list("Cust_Cars").alias("car_list")).select("Cust_No",test_udf("car_list").alias("Final_List")).show(20,False)
输出数据: 客户编号,最终清单

1、丰田|奥迪


2、宝马现代(BMW | Hyundai)在Spark 2.4+中通过
collect_list()
array_join()
的帮助,这变得更加简单

这里是PySpark中的一个演示,不过Scala的代码也应该非常类似:

从pyspark.sql.functions导入数组\加入、收集\列表
friends=spark.createDataFrame(
[
(“雅克”、“尼古拉斯”),
(“雅克”、“乔治”),
(“雅克”、“弗朗索瓦”),
(“鲍勃”、“艾米莉”),
(‘鲍勃’、‘佐伊’),
],
schema=['username','friend'],
)
(
朋友
.orderBy('friend',升序=False)
.groupBy('用户名')
阿格先生(
数组连接(
收集列表(“朋友”),
分隔符=',',
).alias('朋友')
)
.show(truncate=False)
)
输出:

+--------+--------------------------+
|username|friends                   |
+--------+--------------------------+
|jacques |nicolas, georges, francois|
|bob     |zoe, amelie               |
+--------+--------------------------+
这与MySQL和Redshift的类似。

——带有collect\u集的spark SQL解析

选择id、concat_ws('、')、sort_数组(collect_set(colors)))作为csv_颜色
由(
值('A','green'),('A','yellow'),('B','blue'),('B','green'))
)as T(id、颜色)
按id分组

您还可以使用Spark SQL函数collect\u list,之后需要转换为字符串,并使用函数regexp\u replace替换特殊字符

regexp_replace(regexp_replace(regexp_replace(cast(collect_list((column)) as string), ' ', ''), ',', '|'), '[^A-Z0-9|]', '')

这是一种更简单的方法。

如果我想在SQL中使用它怎么办?如何在Spark SQL中注册此自定义项?@MurtazaKanchwala,这样它就可以作为标准自定义项工作。@zero323在Spark SQL 1.4.1中有任何方法可以执行同样的操作吗?您不能在求值函数中删除'UTF8String.fromString()'。这是一个v。很好的解决方案。经过几次修改后,我尝试了它,效果很好,只是我遇到了与结果DF的兼容性问题。如果没有UTF异常,我无法将生成的列与其他列进行比较。我改为将DF转换为RDD;做我想做的,然后将其转换回DF。这解决了所有问题,此外,解决方案的速度提高了10倍。我认为可以肯定地说,如果可能的话,应该避免使用
udf
collect\u set
也可以,只返回唯一的值
collect\u list
collect\u set
都是很棒的Spark SQL函数!如果您使用的是Spark 2.4+,则可以结合使用
collect\u list()
和<