SPARK SQL替换mysql GROUP_CONCAT聚合函数

时间:2015-07-26 18:55:37

标签: apache-spark aggregate-functions apache-spark-sql

我有一个包含两个字符串类型列(用户名,朋友)的表,对于每个用户名,我想在一行中收集所有朋友,连接成字符串(' username1' ,' friends1,friends2,friends3')。我知道MySql通过GROUP_CONCAT做到这一点,有没有办法用SPARK SQL做到这一点?

谢谢

10 个答案:

答案 0 :(得分:39)

继续之前:此操作是另一个groupByKey。虽然它有多个合法的应用程序,但它相对昂贵,所以一定要在必要时使用它。

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

object GroupConcat extends UserDefinedAggregateFunction {
    def inputSchema = new StructType().add("x", StringType)
    def bufferSchema = new StructType().add("buff", ArrayType(StringType))
    def dataType = StringType
    def deterministic = true 

    def initialize(buffer: MutableAggregationBuffer) = {
      buffer.update(0, ArrayBuffer.empty[String])
    }

    def update(buffer: MutableAggregationBuffer, input: Row) = {
      if (!input.isNullAt(0)) 
        buffer.update(0, buffer.getSeq[String](0) :+ input.getString(0))
    }

    def merge(buffer1: MutableAggregationBuffer, buffer2: Row) = {
      buffer1.update(0, buffer1.getSeq[String](0) ++ buffer2.getSeq[String](0))
    }

    def evaluate(buffer: Row) = UTF8String.fromString(
      buffer.getSeq[String](0).mkString(","))
}

使用示例:

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

df.groupBy($"username").agg(GroupConcat($"friend")).show

## +---------+---------------+
## | username|        friends|
## +---------+---------------+
## |username1|friend1,friend2|
## |username2|friend1,friend3|
## +---------+---------------+

您还可以创建一个Python包装器,如Spark: How to map Python with Scala or Java User Defined Functions?

所示

实际上,提取RDD,groupByKeymkString并重建DataFrame可能会更快。

通过将collect_list函数(Spark> = 1.6.0)与concat_ws相结合,您可以获得类似的效果:

import org.apache.spark.sql.functions.{collect_list, udf, lit}

df.groupBy($"username")
  .agg(concat_ws(",", collect_list($"friend")).alias("friends"))

答案 1 :(得分:15)

您可以尝试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")

答案 2 :(得分:5)

以下是您可以在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

答案 3 :(得分:3)

使用pyspark<来实现它的一种方法1.6,遗憾的是它不支持用户定义的聚合函数:

byUsername = df.rdd.reduceByKey(lambda x, y: x + ", " + y)

如果你想再次成为数据框:

sqlContext.createDataFrame(byUsername, ["username", "friends"])

从1.6开始,您可以使用collect_list然后加入创建的列表:

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"))

答案 4 :(得分:2)

语言:Scala Spark版本:1.5.2

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

DF

答案 5 :(得分:1)

在Spark 2.4+中,借助collect_list()array_join(),这变得更加简单。

这是PySpark中的一个演示,尽管Scala的代码也应该非常相似:

from pyspark.sql.functions import array_join, collect_list

friends = spark.createDataFrame(
    [
        ('jacques', 'nicolas'),
        ('jacques', 'georges'),
        ('jacques', 'francois'),
        ('bob', 'amelie'),
        ('bob', 'zoe'),
    ],
    schema=['username', 'friend'],
)

(
    friends
    .orderBy('friend', ascending=False)
    .groupBy('username')
    .agg(
        array_join(
            collect_list('friend'),
            delimiter=', ',
        ).alias('friends')
    )
    .show(truncate=False)
)

输出:

+--------+--------------------------+
|username|friends                   |
+--------+--------------------------+
|jacques |nicolas, georges, francois|
|bob     |zoe, amelie               |
+--------+--------------------------+

这类似于MySQL的GROUP_CONCAT()和Redshift的LISTAGG()

答案 6 :(得分:0)

下面实现了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)

输出数据: Cust_No,Final_List

1,丰田|奥迪

2,宝马|现代

答案 7 :(得分:0)

-使用collect_set的Spark SQL解析

SELECT id, concat_ws(', ', sort_array( collect_set(colors))) as csv_colors
FROM ( 
  VALUES ('A', 'green'),('A','yellow'),('B', 'blue'),('B','green') 
) as T (id, colors)
GROUP BY id

答案 8 :(得分:0)

您也可以使用 Spark SQL 函数 collect_list,之后您需要转换为字符串并使用函数 regexp_replace 来替换特殊字符。

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

这是一种更简单的方法。

答案 9 :(得分:0)

高阶函数 concat_ws()collect_list()groupBy() 一起是不错的选择

import pyspark.sql.functions as F
    
df_grp = df.groupby("agg_col").agg(F.concat_ws("#;", F.collect_list(df.time)).alias("time"), F.concat_ws("#;", F.collect_list(df.status)).alias("status"), F.concat_ws("#;", F.collect_list(df.llamaType)).alias("llamaType"))

样本输出

+-------+------------------+----------------+---------------------+
|agg_col|time              |status          |llamaType            |
+-------+------------------+----------------+---------------------+
|1      |5-1-2020#;6-2-2020|Running#;Sitting|red llama#;blue llama|
+-------+------------------+----------------+---------------------+