| # | Lesson | Function(s) | Open |
|---|---|---|---|
| 01 | ROW_NUMBER | ROW_NUMBER() |
.md · .sql |
| 02 | RANK | RANK() |
.md · .sql |
| 03 | DENSE_RANK | DENSE_RANK() |
.md · .sql |
| 04 | PARTITION BY | PARTITION BY |
.md · .sql |
| 05 | LAG / LEAD | LAG() / LEAD() |
.md · .sql |
| 06 | FIRST/LAST/NTILE | FIRST_VALUE() / LAST_VALUE() / NTILE() |
.md · .sql |
| 07 | Running Totals | SUM() OVER() / AVG() OVER() |
.md · .sql |
Window functions let you perform calculations across a set of rows that are
related to the current row, without collapsing the result set the way
GROUP BY does. They are the single most important tool for turning a
raw SQL developer into someone who can answer real analytics questions:
rankings, leaderboards, running totals, period-over-period comparisons, and
department-wise breakdowns — all in one query.
A window function operates over a “window” of rows defined by an OVER()
clause. Unlike aggregate functions used with GROUP BY, window functions
do not reduce the number of rows returned. Each row keeps its identity
while also gaining access to a calculation performed across its window.
<function_name>(<arguments>) OVER (
[PARTITION BY <column_list>]
[ORDER BY <column_list>]
[<frame_clause>]
)
SELECT
column_a,
column_b,
WINDOW_FUNCTION() OVER (
PARTITION BY grouping_column
ORDER BY sort_column
) AS result_column
FROM table_name;
07_Window_Functions/
│
├── README.md
│
├── 01_ROW_NUMBER.md 01_ROW_NUMBER.sql
├── 02_RANK.md 02_RANK.sql
├── 03_DENSE_RANK.md 03_DENSE_RANK.sql
├── 04_PARTITION_BY.md 04_PARTITION_BY.sql
├── 05_LAG_LEAD.md 05_LAG_LEAD.sql
├── 06_FIRST_LAST_NTILE.md 06_FIRST_LAST_NTILE.sql
├── 07_RUNNING_TOTALS.md 07_RUNNING_TOTALS.sql
│
└── assets/
└── diagrams/
├── window-execution-order.svg
├── row-number-assignment.svg
├── rank-gaps.svg
├── dense-rank-no-gaps.svg
├── partition-by-split.svg
├── lag-lead-offset.svg
├── first-last-ntile.svg
└── running-totals-accumulation.svg
📎 Every lesson file embeds its own diagram inline, right at the top of the file, not just here in the README.
| Domain | Use Case |
|---|---|
| HR | Employee seniority ranking, department leaderboards |
| Finance | Running balances, month-over-month growth |
| Retail | Top-N products per category |
| Banking | Rolling averages for risk monitoring |
| E-commerce | Customer order sequencing, cohort tiers |
By the end of this module you will be able to:
ROW_NUMBER(), RANK(), and DENSE_RANK().PARTITION BY to compute per-group metrics without GROUP BY.LAG()/LEAD().FIRST_VALUE() / LAST_VALUE().NTILE().WHERE cannot filter directly on a window function result, and
how to work around it with a CTE or subquery.| Diagram | Concept |
|---|---|
| SQL logical execution order — where window functions run in the pipeline | |
ROW_NUMBER() — unique sequential numbering |
|
RANK() — competition ranking with gaps after ties |
|
DENSE_RANK() — no gaps after ties |
|
PARTITION BY — independent windows per group |
|
LAG() / LEAD() — looking across rows |
|
FIRST_VALUE() / LAST_VALUE() / NTILE() — boundaries and bucketing |
|
SUM() OVER() — running total accumulation |
ROW_NUMBER() with and without PARTITION BY.RANK() vs DENSE_RANK().SUM() OVER (ORDER BY ...).LAG().WHERE (illegal —
window functions are evaluated after WHERE, so wrap in a CTE/subquery).ORDER BY inside OVER() when using LAG()/LEAD(), which
makes row order (and therefore the result) undefined.LAST_VALUE() without an explicit frame clause
(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), which
silently returns the current row instead of the true last row.RANK() (leaves gaps) with DENSE_RANK() (no gaps).GROUP BY aggregate?WHERE?ROW_NUMBER(), RANK(), and DENSE_RANK() on a tied dataset.06_CTEs (Common Table Expressions)03_Joins02_Aggregations| Mohammad Ammar — Co-Founder @ Apex Analyticx, Data Analytics Engineer, author of the SQL Engineering Handbook (20+ modules). Based in Nagpur, India. |