Spark的dropDuplicates或distinct 对数据去重

消除重复的数据可以通过使用 distinct 和 dropDuplicates 两个方法。

distinct数据去重

distinct 是所有的列进行去重的操作,假如你的 DataFrame里面有10列,那么只有这10列完全相同才会去重。
使用distinct:返回当前DataFrame中不重复的Row记录。该方法和接下来的dropDuplicates()方法不传入指定字段时的结果相同。

dropDuplicates()y有四个重载方法

  1. 第一个def dropDuplicates(): Dataset[T] = dropDuplicates(this.columns)
    这个方法,不需要传入任何的参数,默认根据所有列进行去重,然后按数据行的顺序保留每行数据出现的第一条。
 def dropDuplicates(colNames: Array[String]): Dataset[T] = dropDuplicates(colNames.toSeq)
  1. 第二个def dropDuplicates(colNames: Seq[String])
    传入的参数是一个序列。你可以在序列中指定你要根据哪些列的重复元素对数据表进行去重,然后也是返回每一行数据出现的第一条
def dropDuplicates(colNames: Seq[String]): Dataset[T] = withTypedPlan {
    val resolver = sparkSession.sessionState.analyzer.resolver
    val allColumns = queryExecution.analyzed.output
    val groupCols = colNames.toSet.toSeq.flatMap { (colName: String) =>
      // It is possibly there are more than one columns with the same name,
      // so we call filter instead of find.
      val cols = allColumns.filter(col => resolver(col.name, colName))
      if (cols.isEmpty) {
        throw new AnalysisException(
          s"""Cannot resolve column name "$colName" among (${schema.fieldNames.mkString(", ")})""")
      }
      cols
    }
    Deduplicate(groupCols, planWithBarrier)
  }
  1. 第三个def dropDuplicates(colNames: Array[String])
    传入的参数是一个数组,然后方法会把数组转换为序列然后再调用第二个方法。
def dropDuplicates(colNames: Array[String]): Dataset[T] = dropDuplicates(colNames.toSeq)
  1. 第四个def dropDuplicates(col1: String, cols: String*)
    传入的参数为字符串,在方法体内会把你传入的字符串组合成一个序列再调用第二个方法。
  @scala.annotation.varargs
  def dropDuplicates(col1: String, cols: String*): Dataset[T] = {
    val colNames: Seq[String] = col1 +: cols
    dropDuplicates(colNames)
  }

第三和第四个本质上还是调用了第二个方法,所以我们在使用的时候如果需要根据指定的列进行数据去重,可以直接传入一个Seq。

第一个方法默认根据所有列去重,实际上也是调用了第二个方法,然后传入参数this.columns,即所有的列组成的Seq。

所以各位想深究dropDuplicate()去重的核心代码,只需要研究第二个去重方法即可。

特别的

使用dropDuplicate时,可以先排序,去重时,会保留排第一的,如:

val sourceByTime = df.select("url", "name","updatetime").repartition(col("url"))//这一步是为了解决下面注意事项里出现的问题
            .orderBy(desc("updatetime")).dropDuplicates(Seq("url")) 

上面代码,会保留每个url最新的数据。

注意事项

用dropDuplicates可能会出现重复数据,原因:

数据存在多个excuter中,因为spark是分布式计算的,数据在计算的时候会分布在不同的excutor上,使用dropDuplicate去重的时候,可能只是一个excutor内的数据进行了去重,别的excutor上可能还会有重复的数据。

数据是存放在不同分区的,因为spark是分布式计算的,数据在计算的时候会分散在不同的分区中,使用dropDuplicate去重的时候,不同的区分可能还会存在相同的数据。

参考

https://www.cnblogs.com/Jaryer/p/13558701.html
https://blog.youkuaiyun.com/qq_39900031/article/details/115797287
https://www.yangch.net/archives/114

### 使用 `groupBy()` 实现操作 在 Apache Spark 中,`groupBy()` 可以用来对 DataFrame 的行进行分组[^1]。然而,如果目标是对数据执行操作,则可以结合其他方法来完成此任务。 #### 方法一:使用 `dropDuplicates()` 虽然这不是严格意义上的 `groupBy()` 方式,但在大多数情况下,直接调用 `dropDuplicates()` 是更高效的选择。它可以基于指定列整个 DataFrame 来删除复项: ```python from pyspark.sql import SparkSession # 创建 SparkSession spark = SparkSession.builder.appName("example").getOrCreate() data = [("Alice", 20), ("Bob", 30), ("Alice", 20)] columns = ["name", "age"] df = spark.createDataFrame(data, columns) # 删除复记录 distinct_df = df.dropDuplicates(["name"]) distinct_df.show() ``` 上述代码会保留每种名称的第一条记录,并移除其余相同的记录。 --- #### 方法二:通过 `groupBy().agg()` 进行 当需要利用 `groupBy()` 和聚合函数实现时,可以选择某些特定字段作为键值来进行分组,并从中提取唯一的数据组合。例如: ```python from pyspark.sql.functions import first result_df = df.groupBy("name").agg(first("age").alias("age")) result_df.show() ``` 这里通过对 `"name"` 列进行分组,并选取第一个对应的年龄值,从而实现了按名字维度的效果。 需要注意的是,在实际应用过程中可能会涉及 Shuffle 操作,这通常是由于分组者聚合引起的[^2]。因此对于大规模数据集来说,性能优化可能成为一个考虑因素。 --- #### 方法三:借助 `combineByKey()` 自定义逻辑 另一种更为灵活的方式是采用低层次 API —— `combineByKey()` ,该方法允许开发者自定义创建初始状态以及更新规则的方法[^4]^5]。下面展示了一个简单的例子说明如何运用它来做基本形式上的“”。 假设有一个由 `(key,value)` 对组成的 RDD 数据源: ```scala val rdd = sc.parallelize(Array(("a",1),("b",2),("a",3))) // 定义三个参数分别为初始化器、累加器和合并器 rdd.combineByKey( v => Set(v), (acc:Set[Int],v) => acc + v, (set1:Set[Int], set2:Set[Int]) => set1 ++ set2 ).collect.foreach(println) ``` 在这个案例里,我们把所有的 value 放入到一个不可变集合(Set)当中,这样自然就达到了除相同元素的目的。 --- ### 性能考量与注意事项 - **Shuffle 开销**: 当涉及到诸如 `groupBy`, `reduceByKey` 等算子时,不可避免会产生 shuffle 动作,这对资源消耗较大尤其是处理海量数据场景下应格外注意其影响程度。 - **内存占用**: 如果 keys 数量巨大可能导致过多的小文件生成进而增加 GC 负担等问题发生;此时可适当调整分区数目加以缓解压力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值