Lesson Description
Control exactly which rows participate in each window calculation using explicit frame clauses. You will master ROWS BETWEEN patterns for running, cumulative, and moving metrics.
Lesson Description
Control exactly which rows participate in each window calculation using explicit frame clauses. You will master ROWS BETWEEN patterns for running, cumulative, and moving metrics.
Easy Project
Mini project: Build a compensation ranking dashboard query with department-level rank and delta from the top earner.
By default, aggregate window functions accumulate from the start of the partition to the current row. A frame clause lets you control exactly which rows are included.
function() OVER (
PARTITION BY col
ORDER BY col
ROWS BETWEEN <start> AND <end>
)
| Keyword | Meaning |
|---|---|
UNBOUNDED PRECEDING | First row of partition |
n PRECEDING | n rows before current row |
CURRENT ROW | The current row |
n FOLLOWING | n rows after current row |
UNBOUNDED FOLLOWING | Last row of partition |
SELECT
hire_date, salary,
SUM(salary) OVER (
ORDER BY hire_date, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM employees
ORDER BY hire_date, id;
This is the default frame when ORDER BY is present — writing it explicitly is optional but clarifies intent.
SELECT
hire_date, salary,
ROUND(AVG(salary) OVER (
ORDER BY hire_date, id
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
), 2) AS moving_avg_3
FROM employees
ORDER BY hire_date, id;
SELECT
department_id, salary,
ROUND(
salary / SUM(salary) OVER (
PARTITION BY department_id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) * 100, 2
) AS pct_of_dept
FROM employees
ORDER BY department_id, salary DESC;
| OVER() content | Default frame |
|---|---|
| No ORDER BY | Entire partition |
| Has ORDER BY | UNBOUNDED PRECEDING TO CURRENT ROW |
RANGE BETWEEN instead of ROWS BETWEEN when you want to include all rows with the same ORDER BY value as the current row.ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING gives the full-partition total on every row — useful for percentage calculations.This lesson is part of Window Functions. Focus on the core idea in Window frames — ROWS BETWEEN, then validate with deliberate practice.
What to master
Common mistakes
High-level strategy
Task ladder
Transparent data checks
Retention loop
Logical reasoning for commands
WHERE
Why: Limits rows to only the business-relevant subset.
Memory cue: Filter early to reduce noise.
ORDER BY
Why: Makes output deterministic and reviewable.
Memory cue: No ORDER BY means no guaranteed row order.
AS alias
Why: Makes output columns readable for teams and reports.
Memory cue: If the name is clear, the query is easier to trust.
Concept check
3 quick questions. One at a time. Instant score at the end.
Return first_name, last_name, hire_date, salary, and running_total — a cumulative sum of salary ordered by hire_date, then id. Use ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
No results yet
Run a query to see results here
Return first_name, last_name, hire_date, salary, and moving_avg — the average salary across the current employee and 1 row before and 1 row after (ordered by hire_date, id). Round to 2 decimal places.
No results yet
Run a query to see results here
Return first_name, last_name, department_id, salary, and pct_of_dept — each employee's salary as a percentage of their department's total salary. Use ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. Round to 2 decimal places. Order by department_id, salary descending.
No results yet
Run a query to see results here
Final checkpoint
Pass mark: 80%. Review each explanation after you submit.
1. Which clause should you use to filter grouped results?
2. What is the safest way to avoid accidental row multiplication in joins?
3. Which feature is best for ranking rows within partitions?
4. Why should final analytical queries usually include ORDER BY?
5. When query logic gets complex, what improves readability most?