Spark中如何去重但保留含Null值的行?类比Pandas去重逻辑
在Spark中实现去重时忽略Null值的需求
这个问题我之前也碰到过——Spark的dropDuplicates默认会把null值视为完全相同的重复项,所以直接用dropDuplicates("animal")会把你示例中两个animal为null的行合并掉,完全不符合你想要保留所有含null行的需求。不过我们可以通过两种方式来实现你的目标:
方法一:拆分数据后合并(简单直接)
核心思路是把数据分成含null的行和非null的行两部分,单独处理后再合并:
- 筛选出所有
animal为null的行,这部分我们直接保留所有数据 - 筛选出
animal非null的行,用dropDuplicates("animal")去重 - 将两部分数据合并
具体代码如下:
import org.apache.spark.sql.functions.col // 拆分出animal为null的所有行 val nullAnimalRows = df1.filter(col("animal").isNull) // 拆分出animal非null的行并去重 val dedupedNonNullAnimalRows = df1.filter(col("animal").isNotNull).dropDuplicates("animal") // 合并两个数据集 val finalDf = nullAnimalRows.union(dedupedNonNullAnimalRows) finalDf.show()
运行后你会看到:两个animal为null的行都被保留,而panda的重复行只保留了一条,完全符合你的需求。
方法二:使用窗口函数(灵活控制保留哪一行)
如果你的需求不止是简单去重,还想指定保留重复项中的某一行(比如保留id最小的行),窗口函数会更适合:
import org.apache.spark.sql.expressions.Window import org.apache.spark.sql.functions.row_number // 按animal分区,按id排序,给每个分区的行编号 val windowSpec = Window.partitionBy("animal").orderBy("id") val finalDf = df1 .withColumn("row_num", row_number().over(windowSpec)) // 保留编号为1的行(每个animal的第一行,这里是id最小的),同时保留所有animal为null的行 .filter(col("row_num") === 1 || col("animal").isNull) .drop("row_num") finalDf.show()
这种方式可以精准控制重复项中保留哪一行,比第一种方法更灵活。
为什么直接用dropDuplicates不行?
Spark中null值在去重逻辑里被判定为相等的,所以当你调用dropDuplicates("animal")时,所有animal为null的行都会被视为重复项,只会保留其中一行——这就是你之前尝试时遇到的问题。
内容的提问来源于stack exchange,提问作者nrvaller




