如何在Spark Scala中为唯一姓名分配连续递增ID?
解决Spark Scala中基于姓名分配连续唯一ID的问题
我刚好处理过类似的去标识化需求,给你两种实用的实现方案,都能满足「相同姓名组合分配同一连续ID、不同组合分配新ID」的要求,而且ID从0或1开始递增,完全能和其他用monotonically_increasing_id()生成ID的DataFrame配合使用。
方案一:用窗口函数快速实现(代码简洁,适合小数据集)
核心思路是先把firstname和lastname拼接成唯一的姓名组合,再用dense_rank()窗口函数给每个组合分配连续ID——这个函数会自动给相同的组合分配同一个排名,而且排名是连续无间隔的。
import org.apache.spark.sql.functions.{concat, lit, dense_rank} import org.apache.spark.sql.expressions.Window // 假设你的原始DataFrame名为rawDf // 1. 定义窗口规则:按拼接后的姓名组合排序 val nameWindow = Window.orderBy(concat($"firstname", lit("_"), $"lastname")) // 2. 生成新ID,这里减1让ID从0开始,去掉减1则从1开始 val resultDf = rawDf .withColumn("full_name", concat($"firstname", lit("_"), $"lastname")) // 临时拼接姓名 .withColumn("new_id", dense_rank().over(nameWindow) - 1) .drop("full_name", "id") // 移除临时列和原ID列 .withColumnRenamed("new_id", "id") // 将新ID重命名为id
运行后你的DataFrame会变成这样:
+-------+--------+---------+---------+--------+-----------------+---+ |address|lastname|firstname|patientid|policyno|visitid |id | +-------+--------+---------+---------+--------+-----------------+---+ |addr1 |Pits |Rodney |patid1 |policy1 |visituid_rodney_1|0 | |addr1 |Pits |Rodney |patid1 |policy1 |visituid_rodney_2|0 | |addr1 |Pits |Rodney |patid1 |policy1 |visituid_rodney_3|0 | |addr2 |Dobs |Aliya |patid2 |policy2 |visituid_aliya_1 |1 | |addr2 |Dobs |Aliya |patid2 |policy2 |visituid_aliya_2 |1 | |addr2 |Dobs |OP |patid3 |policy3 |visituid_OP_1 |2 | +-------+--------+---------+---------+--------+-----------------+---+
方案二:先去重再关联(性能更优,适合大数据集)
如果你的数据集很大,窗口函数可能会带来较多的shuffle开销,这时可以先提取所有唯一的姓名组合,给它们分配连续ID后再关联回原数据,这样处理的数据量会小很多。
import org.apache.spark.sql.functions.{concat, lit} // 1. 提取所有唯一的姓名组合,可选orderBy保证ID顺序稳定 val uniqueNames = rawDf .select(concat($"firstname", lit("_"), $"lastname").alias("full_name")) .distinct() .orderBy("full_name") // 2. 给每个唯一姓名分配从0开始的连续ID val nameWithId = uniqueNames .rdd.zipWithIndex() .map { case (row, idx) => (row.getString(0), idx) } .toDF("full_name", "id") // 3. 关联回原DataFrame,替换原ID列 val resultDf = rawDf .withColumn("full_name", concat($"firstname", lit("_"), $"lastname")) .join(nameWithId, Seq("full_name"), "left") .drop("full_name", "id") // 移除临时列和原ID .withColumnRenamed("id", "id")
注意事项
- 如果不需要固定的ID顺序,可以去掉
orderBy步骤,能进一步提升性能; - 两种方案生成的ID都是连续递增的,完全可以作为和其他DataFrame连接的索引键;
- 拼接姓名时用
_分隔是为了避免出现类似firstname=A, lastname=BC和firstname=AB, lastname=C被误判为同一组合的情况,你也可以换成其他不会出现在姓名里的分隔符。
内容的提问来源于stack exchange,提问作者User9102d82
相关产品推荐
相关产品推荐

