OLAP specifications in DB2 for z/OS let you compute ranks and running aggregates without collapsing the result the way GROUP BY does. The window lives inside OVER: PARTITION BY slices the input, ORDER BY sequences each slice, and a ROWS or RANGE frame decides which neighbours participate in an aggregate. This page is the frame itself—how window ORDER BY differs from query ORDER BY, what the default frame is, and when ROWS and RANGE disagree.
IBM splits OLAP syntax into ordered specifications, numbering, and aggregation:
12345678SELECT WORKDEPT, LASTNAME, SALARY, RANK() OVER (PARTITION BY WORKDEPT ORDER BY SALARY DESC) AS RK, SUM(SALARY) OVER ( PARTITION BY WORKDEPT ORDER BY LASTNAME ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS RUNNING_PAY FROM DSN8C10.EMP;
RANK looks at the whole partition ordered by salary. SUM looks only at the frame: here, every row from the start of the department through the current row in last-name order. Both functions return one value per input row. GROUP BY would have returned one row per department.
PARTITION BY expression-list splits the FROM/WHERE result into independent windows. Each partition is framed and ordered on its own. Omit PARTITION BY and the entire result is one partition (a grand running total, a single RANK list).
Partition expressions can be columns or expressions. Changing a partition key resets RANK and resets a running SUM. Choose the same grain you would have grouped by if you were writing GROUP BY, then keep the detail rows.
Window ORDER BY is not the SELECT ORDER BY. It only defines:
ASC and DESC apply per sort key. NULLS FIRST and NULLS LAST control where null keys sit. If the ORDER BY keys do not uniquely identify a row, ROW_NUMBER among ties is non-deterministic, and RANGE treats those ties as peers (they share CURRENT ROW for a RANGE frame).
1234SELECT LASTNAME, SALARY, ROW_NUMBER() OVER (ORDER BY SALARY DESC, LASTNAME) AS N FROM DSN8C10.EMP WHERE WORKDEPT = 'D11';
Adding LASTNAME makes the numbering stable. The SELECT still needs ORDER BY N or ORDER BY SALARY DESC if you want the displayed grid in that sequence. Windows do not replace the query ORDER BY.
A frame is the subset of the partition that an aggregate sees for the current row. Syntax shapes:
12345ROWS UNBOUNDED PRECEDING ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
| Bound | Meaning |
|---|---|
| UNBOUNDED PRECEDING | First row of the partition |
| UNBOUNDED FOLLOWING | Last row of the partition |
| CURRENT ROW | The row being computed (RANGE also includes peers of this ORDER BY value) |
| n PRECEDING | ROWS: n rows before. RANGE: rows whose key is n less than the current key |
| n FOLLOWING | ROWS: n rows after. RANGE: rows whose key is n greater than the current key |
ROWS counts rows in window order, ignoring whether keys are equal. ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING is a three-row sliding window (previous, current, next), truncated at partition edges. Use ROWS for moving averages on a report sequence, or for running totals that must not jump ahead when salaries tie.
RANGE is value-based. CURRENT ROW means “this ORDER BY value,” so all peers enter the frame together. RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW for ORDER BY SALARY includes every row with salary less than or equal to the current salary (in ASC). An unsigned-constant RANGE offset (RANGE 1000 PRECEDING) measures distance on a single numeric or datetime ORDER BY expression—not on a two-column sort.
1234567891011SELECT LASTNAME, SALARY, SUM(SALARY) OVER ( ORDER BY SALARY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS RANGE_RUN, SUM(SALARY) OVER ( ORDER BY SALARY ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS ROWS_RUN FROM DSN8C10.EMP WHERE WORKDEPT = 'D11';
If two D11 employees share a salary, RANGE_RUN is the same on both rows (both peers are inside CURRENT ROW). ROWS_RUN differs: the second physical row also adds the first peer’s salary, then the next adds again. That difference is the usual interview question.
Whole-partition total on every detail row:
1SUM(SALARY) OVER (PARTITION BY WORKDEPT)
Three-row moving average in hire-date order:
12345AVG(SALARY) OVER ( PARTITION BY WORKDEPT ORDER BY HIREDATE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING )
At the first row of a partition there is no preceding row; at the last there is no following row. The frame shrinks. COUNT(*) OVER (same frame) tells you how many rows actually participated if you need to scale the average yourself.
Frames cannot reach outside the partition. ROWS 100 PRECEDING on a 5-row partition only sees those five rows. That is intentional: PARTITION BY is a hard wall.
Imagine kids lined up by class (PARTITION BY), then by height (ORDER BY). RANK is handing out place ribbons for the whole class line. A window frame is a sliding cardboard sleeve that covers only some kids while you add up their pocket money. ROWS says “the sleeve covers three kids in the line, period.” RANGE says “the sleeve covers everyone who is the same height as this kid, plus everyone shorter, if we started from the front.” The class still has every kid in the photo. You did not squash them into one “class total” row the way GROUP BY would.
1. Which OLAP forms use a ROWS/RANGE window frame on Db2 for z/OS?
2. What is the default frame when an aggregate OVER clause has ORDER BY but no ROWS/RANGE?
3. How does ROWS differ from RANGE?
4. Is the window ORDER BY the same as the query ORDER BY?
5. What does UNBOUNDED FOLLOWING mean?