row_number / rank
Number and rank rows inside a window, and how the three ranking functions treat ties.
On this page
Show code in
Every code block on the page follows this.
You will learn
- What a window is, and how it differs from groupBy
- How row_number, rank and dense_rank treat ties
- The top-N-per-group pattern
- What a window costs: shuffle plus sort
row_number gives 1, 2, 3 with no ties; rank gives tied rows the same number and leaves gaps; dense_rank leaves no gaps.What a window is
groupBy turns each group into one row. A window keeps every row and adds a column computed over that row's group, called its partition, in a given order. You define it once with Window.partitionBy(...).orderBy(...) and apply functions with .over(w).
groupBy + agg
Window function
Three ways to number rows
Ranking players by points within each team. Bo and Cy tie on 85:
| player | points | row_number | rank | dense_rank |
|---|---|---|---|---|
| Ana | 90 | 1 | 1 | 1 |
| Bo | 85 | 2 | 2 | 2 |
| Cy | 85 | 3 | 2 | 2 |
| Di | 70 | 4 | 4 | 3 |
- row_number always counts 1, 2, 3, 4. For the tie it picks an order arbitrarily: Bo could be 3 tomorrow.
- rank gives ties the same rank and skips the next numbers, like a sports ranking: two second places, then fourth.
- dense_rank gives ties the same rank without gaps: 1, 2, 2, 3.
Run the example
from pyspark.sql import functions as F from pyspark.sql.window import Window w = Window.partitionBy("team").orderBy(F.col("points").desc()) result = scores.select( "team", "player", "points", F.row_number().over(w).alias("row_number"), F.rank().over(w).alias("rank"), F.dense_rank().over(w).alias("dense_rank"), )
SELECT team, player, points, ROW_NUMBER() OVER w AS row_number, RANK() OVER w AS rank, DENSE_RANK() OVER w AS dense_rank FROM scores WINDOW w AS (PARTITION BY team ORDER BY points DESC)
Switch to PySpark to edit and run this example in your browser.
Top N per group
The classic interview pattern: rank inside each group, then filter. The ranking function you choose decides what happens at the cut-off.
from pyspark.sql import functions as F from pyspark.sql.window import Window w = Window.partitionBy("team").orderBy(F.col("points").desc()) result = (scores .withColumn("r", F.dense_rank().over(w)) .filter(F.col("r") <= 2))
SELECT * FROM ( SELECT *, DENSE_RANK() OVER ( PARTITION BY team ORDER BY points DESC) AS r FROM scores) WHERE r <= 2
Switch to PySpark to edit and run this example in your browser.
| Question asks for | Use |
|---|---|
| Exactly N rows per group | row_number, with a tiebreaker in the order |
| Everyone in the top N positions, ties included | rank |
| The top N distinct values | dense_rank |
Under the hood
A window needs all rows of a partition together and sorted. Spark shuffles by the partition keys, then sorts each partition by the partition and order keys, then streams through the sorted rows. In the plan: Exchange hashpartitioning(team) → Sort → Window.
partitionBy moves every row to a single partition, and one task does all the work. Spark warns "No Partition Defined for Window operation!". On big data that is a guaranteed slow job or out-of-memory error.Several window functions over the same window spec share one shuffle and one sort, so computing row_number, rank and lag together is cheap. A different partitionBy means another shuffle.
Common mistakes
Using row_number when ties matter
A window without partitionBy
Expecting the window order to order the output
Key takeaways
- Window functions keep every row and add a value computed over its partition.
row_numberhas no ties,rankleaves gaps,dense_rankdoes not.- Top N per group is rank-then-filter; choose the function by how ties should behave.
- A window costs a shuffle and a sort; always partition it.
Check yourself
3 questions1. Values in order are 100, 90, 90, 80. What does rank() give the 80?
Show the answer
4. rank gives the two 90s rank 2 and skips 3, so 80 is 4th. dense_rank would give 3.
2. You need exactly one row per customer: their latest order. Which function?
Show the answer
row_number with a tiebreaker. row_number guarantees one row per partition; a tiebreaker in the order makes the choice deterministic.
3. What happens with Window.orderBy("ts") and no partitionBy on a 1 TB table?
Show the answer
All rows are moved to one partition and processed by one task. Without partition keys the whole dataset is one window partition, so one task processes everything.
Practice it
Interview problems that use row_number / rank: write the PySpark, run it, and get graded on hidden tests.
- Latest Order per Customer Medium
- Top Two Salary Levels per Department Medium
- Three-Day Login Streak Hard
- Consecutive Status Runs (Gaps and Islands) Hard
- Upsert: Merge New Records into a Table Hard
Go deeper
Primary sources: functions.row_number · Window functions (SQL)