J'ai un tableau de deux colonnes de type chaîne (nom d'utilisateur, ami) et pour chaque nom d'utilisateur, je veux collecter tous ses amis sur une ligne, concaténés sous forme de chaînes (`` nom d'utilisateur1 '', `` amis1, amis2, amis3 ''). Je sais que MySql le fait par GROUP_CONCAT, est-il possible de le faire avec SPARK SQL?
Merci
Avant de continuer: Cette opération est encore une autre groupByKey
. Bien qu'il possède plusieurs applications légitimes, il est relativement coûteux, alors assurez-vous de ne l'utiliser que lorsque cela est nécessaire.
Solution pas exactement concise ou efficace mais vous pouvez utiliser UserDefinedAggregateFunction
introduit dans Spark 1.5.0:
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(","))
}
Exemple d'utilisation:
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|
## +---------+---------------+
Vous pouvez également créer un wrapper Python comme indiqué dans Spark: comment mapper Python avec Scala ou Java Fonctions définies par l'utilisateur?
En pratique, il peut être plus rapide d'extraire RDD, groupByKey
, mkString
et de reconstruire DataFrame.
Vous pouvez obtenir un effet similaire en combinant collect_list
fonction (Spark> = 1.6.0) avec concat_ws
:
import org.Apache.spark.sql.functions.{collect_list, udf, lit}
df.groupBy($"username")
.agg(concat_ws(",", collect_list($"friend")).alias("friends"))
Vous pouvez essayer la fonction collect_list
sqlContext.sql("select A, collect_list(B), collect_list(C) from Table1 group by A
Ou vous pouvez enregistrer un UDF quelque chose comme
sqlContext.udf.register("myzip",(a:Long,b:Long)=>(a+","+b))
et vous pouvez utiliser cette fonction dans la requête
sqlConttext.sql("select A,collect_list(myzip(B,C)) from tbl group by A")
Voici une fonction que vous pouvez utiliser dans 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'))
En SQL:
select username, concat_ws(',', collect_list(friends)) as friends
from table
group by username
Une façon de le faire avec pyspark <1.6, qui malheureusement ne prend pas en charge la fonction d'agrégation définie par l'utilisateur:
byUsername = df.rdd.reduceByKey(lambda x, y: x + ", " + y)
et si vous voulez en faire à nouveau une trame de données:
sqlContext.createDataFrame(byUsername, ["username", "friends"])
Depuis 1.6, vous pouvez utiliser collect_list puis rejoindre la liste créée:
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"))
Langue: Scala version Spark: 1.5.2
J'ai eu le même problème et j'ai également essayé de le résoudre à l'aide de udfs
mais, malheureusement, cela a entraîné plus de problèmes plus tard dans le code en raison d'incohérences de type. J'ai pu contourner ce problème en convertissant d'abord le DF
en RDD
puis regroupement par et en manipulant les données de la manière souhaitée, puis en convertissant le RDD
retour à un DF
comme suit:
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|
+---------+---------------+
Ci-dessous le code basé sur python qui permet d'atteindre la fonctionnalité group_concat.
Des données d'entrée:
Cust_No, Cust_Cars
1, Toyota
2, BMW
1, Audi
2, Hyundai
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)
Données de sortie: Cust_No, Final_List
1, Toyota | Audi
2, BMW | Hyundai