SQL: WINDOW FUNCTIONS
Master advanced analytics without collapsing row detail: the OVER clause, partitions, ranking functions, quartile distribution, offset navigation with LAG and LEAD, running totals, moving averages, and Top N filtering.
1. What are Window Functions?
A window function performs an analytical calculation across a set of table rows that are related to the current row. Unlike a traditional aggregate query with GROUP BY, a window function does not collapse multiple rows into a single summary record. Each input row retains its distinct identity, while receiving calculated attributes computed over its analytical window.
Standard SQL defines window functions using the OVER clause. This enables side-by-side comparisons of individual records against group aggregates, such as showing each employee salary next to the company average or ranking student exam results within each classroom.
SQL Query Execution Lifecycle
- Step 1 (FROM & JOIN):Source tables are joined and intermediate working sets are loaded.
- Step 2 (WHERE):Individual rows are filtered out before any grouping occurs.
- Step 3 (GROUP BY & HAVING):Rows are grouped and aggregated summaries are evaluated.
- Step 4 (SELECT & Window Functions):Window functions execute in the SELECT phase over filtered and grouped rows.
- Step 5 (ORDER BY & LIMIT):Final presentation ordering and row count limits are applied.
2. The OVER Clause
The OVER clause is the syntax marker that transforms an aggregate or ranking function into a window function. It defines the subset of rows (the window) that the function inspects for each individual row.
An empty OVER () clause instructs SQL to treat the entire filtered result set as a single window. Every row receives the grand total or overall average alongside its individual fields without requiring a separate subquery or self-join.
Unpartitioned Window Calculation
SQLEvaluate company-wide metrics alongside individual employee salaries
Key Takeaway: Row Identity Preservation
- Zero Collapsing:All 8 employee rows remain in the output, yet each row displays the total count (8) and average salary (98500.00).
- Delta Calculations:Because both values exist on the same row, expressions like salary - AVG(salary) OVER () work directly.
3. PARTITION BY
The PARTITION BY subclause divides the query result set into isolated groups called partitions. The window function calculates values independently within each partition, resetting its counters and accumulators as boundary values transition.
Unlike GROUP BY, dividing rows into partitions does not reduce row counts. If department 1 has 3 employees, all 3 employees appear in the output, each carrying the summary metrics calculated specifically for department 1.
Department-Level Partitioning
SQLCompute department averages while retaining individual employee details
Multiple Partition Columns
- Compound Partitions:You can partition by multiple columns, such as PARTITION BY department_id, status.
- Independent Windows:A single SELECT query can mix different partitions, for example, comparing department metrics against company metrics in adjacent columns.
4. ORDER BY inside OVER
An ORDER BY clause placed inside the OVER (...) parenthesis specifies the sequence in which rows are processed within each partition. This inner ordering is distinct from the final query-level ORDER BY that formats the returned table.
The inner order dictates how ranking functions assign positions, which row counts as first or last, and how running totals accumulate values from preceding rows.
Ordering Within Partitions
SQLAssign sequential positions ordered by salary descending per department
5. ROW_NUMBER()
The ROW_NUMBER() function returns a unique, strictly incrementing integer (1, 2, 3...) for each row within its partition, according to the specified ORDER BY clause.
ROW_NUMBER() never assigns duplicate values within the same partition. Even when two rows share identical values in the sort column, each row receives an incremented number based on internal tie-breaking or physical row placement.
Evaluating ROW_NUMBER() at Multiple Scopes
SQLDemonstrating global ranking against partitioned department sequence
Deterministic Ordering Warning
- Tie Breaking:When sorting on non-unique columns, add a primary key as secondary sort criterion: ORDER BY salary DESC, id ASC.
- Reproducibility:Without a secondary unique column, rows with identical values might receive swapped numbers between executions.
6. RANK() and DENSE_RANK()
When items share the same numerical value, business requirements often demand equal ranking. SQL provides two distinct ranking functions for handling ties:
RANK(): Assigns identical rank numbers to tied values, but skips subsequent rank numbers according to the number of ties (for example: 1, 2, 2, 4).DENSE_RANK(): Assigns identical rank numbers to tied values, but never leaves gaps in the numeric sequence (for example: 1, 2, 2, 3).
Examining Ties with RANK and DENSE_RANK
SQLInspect how ties in course grades produce gap intervals in RANK vs consecutive steps in DENSE_RANK
7. Comparison: RANK vs DENSE_RANK vs ROW_NUMBER
Understanding the distinction between these three ranking functions is essential for accurate analytical queries and pagination routines.
| Student Name | Grade | ROW_NUMBER() | RANK() | DENSE_RANK() |
|---|---|---|---|---|
| Mei Chen | 95 | 1 | 1 | 1 |
| Ayesha Khan | 92 | 2 | 2 | 2 |
| Sofia Rossi | 90 | 3 | 3 | 3 |
| Omar Farooq (Tie) | 90 | 4 | 3 | 3 |
| Daniel Smith | 88 | 5 | 5 (skips 4) | 4 (no gap) |
Selection Criteria for Ranking
- ROW_NUMBER():Use for pagination, de-duplicating rows, or choosing exactly one record per group.
- RANK():Use for Olympic-style podium standings where joint winners push subsequent competitors back.
- DENSE_RANK():Use for prize tiers or salary bands where tied positions do not penalize following tiers.
8. NTILE()
The NTILE(n) function divides an ordered partition into n roughly equal buckets and assigns the bucket number (1 through n) to each row. Common use cases include calculating quartiles (NTILE(4)), deciles (NTILE(10)), or percentiles.
If the row count is not evenly divisible by n, the remainder rows are distributed starting from bucket 1 downward. For example, 7 rows partitioned into 3 buckets results in buckets of size 3, 2, and 2.
Segmenting Salaries with NTILE(3)
SQLDivide staff into top, middle, and entry compensation tiers
9. LAG() and LEAD()
LAG() and LEAD() are positional navigation functions that allow a query to look backwards or forwards relative to the current row without constructing expensive self-joins.
LAG(col, offset, default): Fetches the value fromoffsetrows prior to the current row (defaults to offset 1 and NULL).LEAD(col, offset, default): Fetches the value fromoffsetrows following the current row (defaults to offset 1 and NULL).
Salary Step Analysis with LAG and LEAD
SQLEvaluate compensation intervals between neighboring compensation positions
Default Fallback Values
- Preventing NULLs:Provide a third parameter to avoid NULL on boundary rows: LAG(val, 1, 0) or LEAD(val, 1, current_val).
- Period-Over-Period Growth:Ideal for calculating month-over-month revenue delta: (current_rev - LAG(current_rev, 1)) / LAG(current_rev, 1).
10. FIRST_VALUE, LAST_VALUE, and NTH_VALUE
These value navigation functions return specific edge or index entries from an ordered window:
FIRST_VALUE(col): Returns the value from the first row of the window frame.LAST_VALUE(col): Returns the value from the last row of the window frame.NTH_VALUE(col, n): Returns the value from the n-th row of the window frame.
Department Benchmark with FIRST_VALUE
SQLExpose the highest earning colleague for each department partition
The LAST_VALUE() Window Frame Trap
- The Surprise:By default, LAST_VALUE() often returns the current row value rather than the partition end.
- The Cause:The default frame ends at CURRENT ROW. To reach the actual end of the partition, explicitly declare: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
11. Running Total (Cumulative Sum)
A running total calculates the progressive accumulation of values over time or order sequence. Adding an ORDER BY clause inside an aggregate window function automatically converts it into a running calculation.
Cumulative Payroll Timeline
SQLCalculate accumulated salary commitments as employees were recruited
12. Moving Average
A moving average smooths short-term fluctuations to reveal underlying trends. It computes the mean over a sliding window frame consisting of a fixed number of rows surrounding the current record.
Sliding Moving Average Calculation
SQLSmooth compensation figures using a 3-row moving window frame
13. Window Frames: ROWS BETWEEN and RANGE BETWEEN
A window frame defines the exact slice of rows inside a partition that participates in the calculation for the current row. Frame bounds use five positional qualifiers:
UNBOUNDED PRECEDING: Starts at the very first row of the partition.n PRECEDING: Startsnrows before the current row.CURRENT ROW: The current row being evaluated.n FOLLOWING: Extendsnrows after the current row.UNBOUNDED FOLLOWING: Extends to the very last row of the partition.
| Specification | Frame Basis | Behavior on Tied Values |
|---|---|---|
| ROWS BETWEEN ... | Physical rows | Processes rows individually regardless of tied column values |
| RANGE BETWEEN ... | Logical values | Treats all rows with duplicate sort keys as a unified bundle |
Default Window Frame Specification
- Implicit Default:When ORDER BY is specified without a frame, SQL defaults to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
- Performance Advice:Prefer ROWS over RANGE for running totals to prevent duplicate sort-value bundling and reduce engine evaluation overhead.
14. Aggregate Functions as Window Functions
Standard aggregate functions - SUM, AVG, MIN, MAX, and COUNT - transform into window functions by appending an OVER clause. This allows developers to express ratios and percentages without subqueries.
Department Percentage Contribution
SQLExpress individual compensation as a percentage of total department expenditure
15. GROUP BY vs PARTITION BY
While both operations segment data by categorical attributes, their architectural mechanics and output shapes differ fundamentally:
| Property | GROUP BY | PARTITION BY |
|---|---|---|
| Row Cardinality | Collapses rows into one record per group | Preserves original input row count exactly |
| Non-Aggregated Columns | Must appear in the GROUP BY clause | Any column can be projected freely in SELECT |
| Multiple Groupings | Restricted to a single aggregation level | Can mix multiple partition scopes in one query |
| Filtering Results | Filtered directly via HAVING clause | Requires an outer CTE or subquery for filtering |
16. Top N per Group
A frequent analytical challenge is finding the top N records within each category (such as the top 2 highest earners in each department).
Because window functions run during the SELECT phase after the WHERE clause has already completed, you cannot filter on window functions directly in a WHERE clause. The standard SQL pattern solves this by computing the ranking inside a Common Table Expression (CTE) or derived table subquery, and then applying the filter in the outer query.
Filtering Top N Records with a CTE
SQLRetrieve the top 2 salary tiers for every department using DENSE_RANK
Architectural Pattern Summary
- Step 1 (Rank in CTE):Calculate ROW_NUMBER() or DENSE_RANK() inside an inner WITH expression.
- Step 2 (Filter in Outer Query):Filter using WHERE salary_rank <= N to isolate the desired top tier.
- Ties Consideration:Use ROW_NUMBER() for an exact count limit, or DENSE_RANK() to include tied positions.
Knowledge Check: SQL Window Functions
1. How does a window function differ from a GROUP BY aggregation?
2. What sequence is assigned by RANK() when two rows tie for second place?
3. Why can window functions not be placed directly inside WHERE clauses?
4. What value does the LEAD(salary, 1) function retrieve?
5. What is the default window frame when ORDER BY is supplied without ROWS?
6. Which technique filters rows by window ranking like Top 1 per group?