“Who is the highest paid in each department?” used to mean a correlated subquery or a join to a MAX aggregate. DB2 for z/OS OLAP specifications do it in one pass: RANK, DENSE_RANK, and ROW_NUMBER with an OVER (PARTITION BY ... ORDER BY ...) window. They are not ordinary scalar functions. IBM lists them as OLAP specifications that happen to look like functions. This page teaches the three numbering/ranking forms and PARTITION BY. Window frames (ROWS / RANGE) have their own page.
An OLAP specification computes a value for the current row using other rows in a window. The window is defined by OVER(...). Unlike AVG in a GROUP BY query, the detail rows stay visible. You still see every employee; you also see their rank.
IBM groups three forms:
Restrictions that surprise people coming from other SQL dialects:
123RANK() OVER (PARTITION BY workdept ORDER BY salary DESC) DENSE_RANK() OVER (PARTITION BY workdept ORDER BY salary DESC) ROW_NUMBER() OVER (PARTITION BY workdept ORDER BY salary DESC)
PARTITION BY is not tablespace partitioning. It is “restart the ranking for each department.”
123SELECT WORKDEPT, EMPNO, LASTNAME, SALARY, RANK() OVER (PARTITION BY WORKDEPT ORDER BY SALARY DESC) AS RK FROM DSN8C10.EMP;
Each WORKDEPT value gets its own sequence starting at 1. Two employees in A00 and D11 can both be rank 1. Multiple partition expressions are allowed (PARTITION BY WORKDEPT, JOB) — think of the combination as the window key.
Each column in a partitioning-expression must unambiguously reference a column of the subselect that contains the OLAP specification.
RANK assigns the ordinal rank of a row within its window. Rows that are not distinct on the ORDER BY keys share the same rank. The next distinct value skips ahead so that rank equals “1 + count of rows with a strictly better ordering key.”
1234SELECT LASTNAME, SALARY, RANK() OVER (ORDER BY SALARY DESC) AS RK FROM DSN8C10.EMP WHERE WORKDEPT = 'A00';
DENSE_RANK also ties equal keys, but the next group gets the next integer with no gap. Use DENSE_RANK when you want “medal classes” (gold / silver / bronze) without holes. Use RANK when the number should mean “how many people are strictly ahead of me, plus one.”
ROW_NUMBER assigns 1, 2, 3, … with no shared numbers. If you omit ORDER BY in the window, numbers are assigned in an arbitrary order as rows come back — fine for “give me any unique sequence,” dangerous for “page 2 of a report.” Always specify ORDER BY when the number must be reproducible.
123SELECT EMPNO, LASTNAME, ROW_NUMBER() OVER (ORDER BY LASTNAME, EMPNO) AS RN FROM DSN8C10.EMP;
Add EMPNO (or another unique key) at the end of the window ORDER BY so ties on LASTNAME still produce a stable sequence.
| SALARY | RANK | DENSE_RANK | ROW_NUMBER |
|---|---|---|---|
| 90000 | 1 | 1 | 1 |
| 90000 | 1 | 1 | 2 |
| 80000 | 3 | 2 | 3 |
| 70000 | 4 | 3 | 4 |
Two people at 90000: RANK and DENSE_RANK both show 1. RANK then jumps to 3 for 80000. DENSE_RANK uses 2. ROW_NUMBER never ties.
Because you cannot put RANK in WHERE, nest the query:
123456789SELECT WORKDEPT, EMPNO, LASTNAME, SALARY, RK FROM ( SELECT WORKDEPT, EMPNO, LASTNAME, SALARY, RANK() OVER (PARTITION BY WORKDEPT ORDER BY SALARY DESC) AS RK FROM DSN8C10.EMP ) AS R WHERE RK <= 3 ORDER BY WORKDEPT, RK, EMPNO;
A CTE (WITH) is the same idea and often clearer in production SQL. FETCH FIRST n ROWS ONLY applies to the whole result, not per partition — that is why RANK/ROW_NUMBER plus an outer filter is the pattern for “top 3 per dept.”
They do different jobs. The window ORDER BY decides ranks. The query ORDER BY decides what the user sees. If you omit the outer ORDER BY, Db2 may return ranked rows in any sequence. Always ORDER BY the partition key, then the rank, then a unique tie-breaker for reports.
NULLS FIRST and NULLS LAST on the window sort control whether unknown salaries rank as best or worst. Make that choice explicit in HR queries.
Imagine a sports day. PARTITION BY is “line up by class.” ORDER BY is “tallest first.” RANK gives out place ribbons but if two kids are the same height they share first place and nobody gets second (the next kid is third). DENSE_RANK still shares first, but the next kid is second — no skipped ribbon. ROW_NUMBER hands out numbered stickers 1, 2, 3 even if two kids are the same height — someone just stands a little to the left. You cannot ask “only show ribbon 1” while kids are still lining up (WHERE); you take the photo of everyone with ribbons, then keep the pictures you want.
1. What is the difference between RANK and DENSE_RANK when two rows tie?
2. Does ROW_NUMBER assign the same number to tied rows?
3. PARTITION BY in an OLAP OVER clause:
4. Can RANK be used without ORDER BY in the window?
5. Where can you not put an OLAP specification?