Deduplication and aggregations
kept in this browser
Edit this pageRemoving duplicates with distinct, dropDuplicates, or a window function, and aggregating with groupBy/agg, count, approx_count_distinct, avg, describe, and summary.
Checked against its sources on · history
On this page7
What it is
Deduplication removes repeated rows, either entirely or with respect to a subset of columns; aggregation summarizes groups of rows into a single value (count, average, distinct count). These are the operations that turn silver into a trustworthy dataset and gold into numbers the business can use.
Why it exists
Duplicates show up everywhere: a job retry that rewrites the same batch, a source resending an event, CDC with multiple versions of the same record. Silver (see Medallion architecture: bronze, silver, gold) is where you decide the uniqueness key and the rule for which copy to keep. Aggregations, on the other hand, cost a shuffle: knowing when an approximation (approx_count_distinct) is good enough is a cost decision, not just a precision one.
How it works
Deduplication
| Method | What it compares | Which row survives |
|---|---|---|
distinct() | all columns | any one |
dropDuplicates() | all columns | any one |
dropDuplicates(["k1", "k2"]) | only the listed columns | any one among those sharing the key |
window + row_number() | the partitionBy columns | the one chosen by orderBy |
distinct() and dropDuplicates() with no arguments are equivalent to SELECT DISTINCT. With a subset, dropDuplicates doesn’t guarantee which row survives: in a distributed system, “the first one” has no stable meaning. If keeping the most recent record matters, the only deterministic way is a window function with row_number() ordered by timestamp descending and a = 1 filter.
In streaming, dropDuplicates has to keep state for every key it has ever seen; dropDuplicatesWithinWatermark limits that to the watermark window.
Aggregations
groupBy(...) followed by agg(...) with functions from pyspark.sql.functions:
| Function | SQL | Notes |
|---|---|---|
count("*") | COUNT(*) | count("col") ignores nulls |
count_distinct("col") | COUNT(DISTINCT col) | exact, requires a full shuffle; countDistinct is the legacy alias |
approx_count_distinct("col", rsd) | approx_count_distinct(col) | HyperLogLog, default relative error of 5%, much cheaper |
avg("col") / mean("col") | AVG(col) | synonyms |
sum, min, max, stddev | same |
describe() and summary() are exploratory shortcuts that return a DataFrame: describe computes count, mean, stddev, min, max; summary adds the 25th, 50th, and 75th percentiles and accepts a list of which statistics to compute. Neither is meant for gold tables: they’re for understanding data in a notebook.
groupBy(...).pivot("col") turns a column’s distinct values into columns. Passing the list of values as a second argument avoids an upfront scan.
Example
Silver: keep the latest version of each order. Gold: metrics by channel.
CREATE OR REPLACE TABLE shop.silver.orders AS
SELECT * EXCEPT (rn)
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY updated_at DESC) AS rn
FROM shop.bronze.orders_raw
)
WHERE rn = 1;from pyspark.sql import functions as F
from pyspark.sql.window import Window
raw = spark.read.table("shop.bronze.orders_raw")
w = Window.partitionBy("order_id").orderBy(F.col("updated_at").desc())
latest = (
raw
.withColumn("rn", F.row_number().over(w))
.filter("rn = 1")
.drop("rn")
)
latest.write.mode("overwrite").saveAsTable("shop.silver.orders")When the rule is simply “one row per key, doesn’t matter which”:
deduped = raw.dropDuplicates(["order_id"])
Aggregation by channel:
SELECT
channel,
COUNT(*) AS orders,
approx_count_distinct(customer_id) AS customers_approx,
AVG(amount) AS avg_amount
FROM shop.silver.orders
GROUP BY channel;gold = (
spark.read.table("shop.silver.orders")
.groupBy("channel")
.agg(
F.count("*").alias("orders"),
F.approx_count_distinct("customer_id").alias("customers_approx"),
F.avg("amount").alias("avg_amount"),
)
)Quick stats in a notebook, and a pivot:
spark.read.table("shop.silver.orders").select("amount").summary("count", "mean", "50%", "max").show()
by_month = (
spark.read.table("shop.silver.orders")
.groupBy("channel")
.pivot("order_month", ["2026-07", "2026-08"])
.agg(F.sum("amount"))
)
Compared to pandas, groupBy doesn’t produce an index: the result is a plain DataFrame with the grouping columns. And count() with no groupBy is an action that returns an integer, not a column.
Common mistakes
- Using
dropDuplicates(["order_id"])and assuming it keeps the most recent version: it isn’t guaranteed. You need the window. - Running
count_distincton hundreds of millions of rows for a dashboard counter: it costs a full shuffle whenapprox_count_distinctwould be enough. count("col")instead ofcount("*"): nulls aren’t counted, and the total looks wrong.pivotwithout a list of values: Spark has to read the data twice, and it isn’t supported in streaming at all.- Confusing
df.count()(an action, returns a number) withF.count()(an aggregate function, returns a column).
Check yourself
3 questions on what this page says. Pick an answer, then check it.
Show the answer
C. approx_count_distinct, because the exact count needs a full shuffle and the approximation is much cheaper
count_distinct is exact but requires a full shuffle. approx_count_distinct trades a small relative error for a much lower cost, which the page treats as a cost decision.
Read the section againShow the answer
A. Spark has to read the data twice, and the pivot is not supported in streaming at all
Without the values listed, Spark first needs a pass to find the distinct values, so the data is read twice. The page also notes that pivot is not supported in streaming.
Read the section againShow the answer
A. It must keep every key it has ever seen; dropDuplicatesWithinWatermark limits the state to the watermark window
In streaming, dropDuplicates must remember all keys seen so far. dropDuplicatesWithinWatermark bounds that memory to the watermark window.
Read the section again
Where this sits
- Data Analyst AssociateExecuting queries using Databricks SQL and Databricks SQL Warehouses
- Data Engineer AssociateData Transformation and Modeling22% of the exam“Perform data deduplication operations and aggregate operations on DataFrames, such as count, approximate count distinct, and mean, summary.”
- Data Engineer ProfessionalData Transformation, Cleansing, and Quality
- Machine Learning AssociateData Processing
- Learning pathSQL & Python FoundationsThe SQL and PySpark you need before any other path: the Spark dialect, types, joins, windo…
- Learning pathData EngineeringBuild production pipelines: ingest with Auto Loader, COPY INTO and Lakeflow Connect, trans…
Nothing of that kind here yet. Try the full list.