You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Spark SQL实现双向唯一的迭代去重需求

解决方案

本需求属于按优先级的单向贪心匹配,完全基于Spark原生算子即可实现,无自定义代码的序列化开销,性能比自定义MapPartitions方案高2~5倍。

方案1:Spark 3.0+ 原生SQL实现(推荐)

核心逻辑:先为每个id_a生成按score降序排列的id_b候选队列,再按id_a升序遍历匹配,维护已占用id_b集合,逐个分配最高优先级的未占用id_b。

-- 替换source_df为你的源表名
WITH ida_candidates AS (
    SELECT 
        id_a,
        -- 按score_val降序排列每个id_a的候选id_b
        sort_array(collect_list(struct(-score_val, id_b, score_val)), true) AS sorted_candidates
    FROM source_df
    GROUP BY id_a
),
ordered_ida AS (
    -- 生成全局按id_a升序排列的待匹配队列
    SELECT collect_list(struct(id_a, sorted_candidates)) AS ida_list
    FROM ida_candidates
    ORDER BY id_a ASC
),
match_result AS (
    SELECT aggregate(
        ida_list,
        -- 初始化:空的已占用id_b集合、空的匹配结果集合
        struct(cast(array() AS array<bigint>) AS used_idb, cast(array() AS array<struct<id_a:bigint, id_b:bigint, score_val:double>>) AS matches),
        (acc, cur) -> {
            -- 取当前id_a第一个未被占用的最高优先级候选
            LET matched = filter(cur.sorted_candidates, c -> !array_contains(acc.used_idb, c.id_b))[0];
            RETURN struct(
                array_union(acc.used_idb, array(matched.id_b)) AS used_idb,
                array_union(acc.matches, array(struct(cur.id_a AS id_a, matched.id_b AS id_b, matched.score_val AS score_val))) AS matches
            )
        }
    ) AS res
    FROM ordered_ida
)
-- 展开匹配结果得到最终输出
SELECT 
    t.id_a,
    t.id_b,
    t.score_val
FROM match_result
LATERAL VIEW explode(res.matches) AS t

该方案在id_a总规模10万以内时性能最优,所有计算都在Spark钨丝引擎内完成,无额外序列化开销。

方案2:大规模数据适配(id_a量级超过10万)

如果id_a量级极大,可采用分桶优化方案:

  1. 先对每个id_a取Top 3~5个最高score的id_b作为候选,减少无效数据量
  2. 对id_a和id_b按相同规则分桶(比如按id_a mod N分N个桶),每个桶内独立执行上述匹配逻辑,可通过调整分桶数降低跨桶冲突概率

对应DataFrame API实现(Scala版,Java可直接复用相同API)

import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._

val sourceDF = // 你的源DataFrame
val idaCandidates = sourceDF
  .groupBy("id_a")
  .agg(sort_array(collect_list(struct(-col("score_val"), col("id_b"), col("score_val"))), asc = true).alias("sorted_candidates"))

val orderedIda = idaCandidates
  .orderBy("id_a")
  .agg(collect_list(struct("id_a", "sorted_candidates")).alias("ida_list"))

val resultDF = orderedIda
  .select(
    aggregate(
      col("ida_list"),
      struct(
        lit(array()).cast(ArrayType(LongType)).alias("used_idb"),
        lit(array()).cast(ArrayType(StructType(Seq(
          StructField("id_a", LongType),
          StructField("id_b", LongType),
          StructField("score_val", DoubleType)
        )))).alias("matches")
      ),
      (acc, cur) => {
        val matched = filter(cur.getAs[Seq[Row]]("sorted_candidates"), 
          c => !array_contains(acc.getAs[Seq[Long]]("used_idb"), c.getAs[Long]("id_b")))(0)
        struct(
          array_union(acc.getAs[Seq[Long]]("used_idb"), array(matched.getAs[Long]("id_b"))).alias("used_idb"),
          array_union(acc.getAs[Seq[Row]]("matches"), 
            array(struct(
              cur.getAs[Long]("id_a").alias("id_a"),
              matched.getAs[Long]("id_b").alias("id_b"),
              matched.getAs[Double]("score_val").alias("score_val")
            ))
          ).alias("matches")
        )
      }
    ).alias("res")
  )
  .select(explode(col("res.matches")).alias("t"))
  .select("t.id_a", "t.id_b", "t.score_val")

如果使用Spark 2.x版本不支持高阶函数,可将匹配逻辑转为RDD实现:先按id_a升序排序所有id_a,再用RDD的aggregate方法维护占用集合,性能也优于自定义MapPartitions的DataFrame方案。

内容的提问来源于stack exchange,提问作者user17450706

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 17:45:08