PySpark中高效关联多DataFrame:遍历数组列表匹配Map键值
高效实现三个DataFrame的关联匹配
现有三个DataFrame
Dataframe_1(需遍历每个数组列表)
| Some_id |
|---|
| ["23815", "23824", "36978", "23824", "81"] |
| ["37551", "36091", "81"] |
Dataframe_2(查找表)
| Some_id | Cor_name | PropertiesMap |
|---|---|---|
| 37551 | Mango | {cor_name -> Mango, _value -> 18912} |
| 36091 | Orange | {cor_name -> Orange, _value -> 1234} |
| 23824 | Mango | {cor_name -> Mango, _value -> 29014} |
| 81 | Apple | {cor_name -> Apple, _value -> E7063} |
Dataframe_3(需从中获取匹配的ID)
| Need_id | PropertiesMap |
|---|---|
| 1 | [Mango :18912 ] |
| 2 | [Mango :18912, Apple: E7063 , Orange: 1234] |
| 3 | [Apple : E7063 ] |
| 4 | [Mango :18912, Apple: E7063 , Orange: 1234] |
期望输出
| Need_id | Some_id | PropertiesMap |
|---|---|---|
| 2 | ["37551", "36091", "81"] | [Mango :18912, Apple: E7063 , Orange: 1234] |
| 4 | ["37551", "36091", "81"] | [Mango :18912, Apple: E7063 , Orange: 1234] |
当前低效实现代码
for row in dataframe_1.collect(): lis_1 = list(row) dataframe_2= (dataframe_2.alias("A").join(dataframe_1.alias("B"), on = (F.array_contains("B.some_id", "A.some_id")))) #What to do after
高效解决方案
核心思路
避免将分布式数据拉到Driver端遍历,利用Spark的并行计算能力,通过数组展开→关联查找→聚合生成属性串→格式对齐匹配四个步骤完成,全程在集群Executor端并行执行。
完整代码实现
from pyspark.sql import functions as F # 步骤1:展开Dataframe_1的数组,关联Dataframe_2提取属性 # 炸开数组,将每个Some_id元素拆分为单独行 df1_exploded = dataframe_1.withColumn("single_id", F.explode(F.col("Some_id"))) # 关联查找表,提取Cor_name和对应的_value值 df_joined = df1_exploded.join( dataframe_2.withColumnRenamed("Some_id", "single_id"), on="single_id", how="inner" ).select( F.col("Some_id"), F.col("Cor_name"), # 从PropertiesMap中提取_value的具体值 F.regexp_extract(F.col("PropertiesMap"), r"_value -> (\w+|\d+)", 1).alias("value") ) # 步骤2:按原数组分组,生成和Dataframe_3格式一致的PropertiesMap字符串 # 聚合时排序保证元素顺序统一,避免因顺序不同导致匹配失败 df1_processed = df_joined.groupBy("Some_id").agg( F.concat_ws(", ", F.sort_array(F.collect_list(F.concat(F.col("Cor_name"), F.lit(" :"), F.col("value"))))).alias("PropertiesMap") ) # 步骤3:统一格式后关联Dataframe_3,匹配对应Need_id # 清理Dataframe_3的PropertiesMap,去除多余空格确保匹配一致性 df3_clean = dataframe_3.withColumn( "cleaned_prop", F.trim(F.regexp_replace(F.col("PropertiesMap"), "\s+", " ")) ).withColumn( "cleaned_prop", F.regexp_replace(F.col("cleaned_prop"), ",\s+", ", ") ) # 清理处理后的Dataframe_1的PropertiesMap df1_clean = df1_processed.withColumn( "cleaned_prop", F.trim(F.regexp_replace(F.col("PropertiesMap"), "\s+", " ")) ) # 关联得到最终结果 final_df = df3_clean.join( df1_clean, on="cleaned_prop", how="inner" ).select( F.col("Need_id"), F.col("Some_id"), F.col("PropertiesMap") ).orderBy("Need_id") # 输出结果 final_df.show(truncate=False)
原代码低效原因
collect()操作:将分布式Dataframe拉到Driver端本地内存,大数据量下易引发内存溢出,且本地遍历是单线程,完全浪费Spark的并行计算能力。- 循环内重复关联:每次遍历都执行一次join,多次重复计算,大幅增加IO和计算开销。
优化细节
- 用
explode()替代遍历,将数组展开为分布式行,利用集群节点并行处理。 - 分组聚合仅做一次计算,生成目标格式的属性字符串。
- 统一PropertiesMap的格式(去除多余空格、统一分隔符),避免因格式细节差异导致匹配失败。
内容的提问来源于stack exchange,提问作者sys
相关产品推荐
相关产品推荐

