The beginner GROUP BY page covers one grain and HAVING. This DB2 for z/OS lesson goes further: grouping-expressions, why column positions work in ORDER BY but not GROUP BY, ROLLUP and CUBE as super-groups, the GROUPING function, and aggregate filtering. If you already know GROUPING SETS from the companion page, treat this as the “expressions and report-outline” companion.
A grouping-expression is evaluated for each row of the previous result (after WHERE). Rows with equal expression values share a group. Nulls of that expression form one group.
1234SELECT YEAR(HIREDATE) AS YR, COUNT(*) AS HIRES FROM DSN8C10.EMP GROUP BY YEAR(HIREDATE) ORDER BY YR;
Repeat the expression in GROUP BY. You generally cannot write GROUP BY YR; the alias is assigned after grouping. Concatenation must match the grouping-expression exactly. Arithmetic is picky about parentheses: if you group by COL1+COL2, the select list may use COL1+COL2+3 or 3+(COL1+COL2), but 3+COL1+COL2 may be rejected because associativity does not match.
Illegal in a grouping-expression (use a nested table / CTE first):
1234567891011SELECT BAND, AVG(SALARY) AS AVG_SAL FROM ( SELECT CASE WHEN SALARY < 40000 THEN 'LOW' WHEN SALARY < 70000 THEN 'MID' ELSE 'HIGH' END AS BAND, SALARY FROM DSN8C10.EMP ) AS B GROUP BY BAND;
That CASE is a simple searched CASE on column comparisons, so you could also put the same CASE in GROUP BY. The nested form is the escape hatch when the expression is illegal as a grouping-expression or too ugly to repeat.
People coming from ORDER BY or from other SQL dialects try:
12345678-- ORDER BY: legal — 2 means the second result column SELECT WORKDEPT, COUNT(*) AS N FROM DSN8C10.EMP GROUP BY WORKDEPT ORDER BY 2 DESC; -- GROUP BY 1: not the Db2 for z/OS grouping syntax -- GROUP BY uses expressions, not SELECT-list ordinals
| Clause | Integer column position? |
|---|---|
| ORDER BY | Yes — unsigned integer n is the nth result column |
| GROUP BY | No — use column names or grouping-expressions, not SELECT positions |
| SELECT aliases | Cannot GROUP BY the AS name; it is not a column of FROM |
IBM’s ORDER BY sort-key may be a column name, an unsigned integer (nth column of the result), or a sort-key-expression. GROUP BY has no parallel integer form in the z/OS SQL Reference. If you write GROUP BY 1, you are grouping on the constant 1 (one group for the whole table)—not “first select-list item.” That is a silent logic bug, not a convenience feature. Always name the grouping-expression.
ROLLUP(e1, e2, …, en) is a hierarchy: full detail, then drop the last element, then the last two, ending with the grand total (). Argument order is the outline of the report.
1234567SELECT YEAR(HIREDATE) AS YR, WORKDEPT, COUNT(*) AS N, AVG(SALARY) AS AVG_SAL FROM DSN8C10.EMP GROUP BY ROLLUP (YEAR(HIREDATE), WORKDEPT) ORDER BY YR, WORKDEPT;
You get year+department counts, year subtotals (WORKDEPT null), and a grand total (both keys null). You do not get department totals across all years—that would be CUBE or an extra GROUPING SET.
1GROUP BY ROLLUP (WORKDEPT, (YEAR(HIREDATE), MONTH(HIREDATE)))
YEAR and MONTH drop together. Sets: (dept, year, month), (dept), (). There is no dept+year without month unless you add it yourself with GROUPING SETS.
CUBE builds every subset of its elements, including the empty set. Two elements → four grouping sets; three → eight. Cost and row count grow as 2^n.
| Specification | Grouping sets |
|---|---|
| ROLLUP(A, B) | (A,B), (A), () |
| CUBE(A, B) | (A,B), (A), (B), () |
| ROLLUP((A, B), C) | (A,B,C), (A,B), () |
1234SELECT WORKDEPT, JOB, COUNT(*) AS N FROM DSN8C10.EMP GROUP BY CUBE (WORKDEPT, JOB) ORDER BY WORKDEPT, JOB;
Besides (WORKDEPT, JOB) you get WORKDEPT-only, JOB-only, and grand total. That is the cross-tab. Prefer ROLLUP when the business has a single drill path (year → month → day). Prefer CUBE for a small number of independent dimensions. Prefer GROUPING SETS when you need a custom mix and CUBE would over-produce.
Super-group nulls look like missing keys. GROUPING(expression) (SYSIBM) matches a grouping-expression of the same subselect and returns SMALLINT:
1234567891011121314SELECT CASE GROUPING(WORKDEPT) WHEN 1 THEN 'ALL DEPTS' ELSE WORKDEPT END AS DEPT, CASE GROUPING(JOB) WHEN 1 THEN 'ALL JOBS' ELSE JOB END AS JOB, COUNT(*) AS N, GROUPING(WORKDEPT) AS G_DEPT, GROUPING(JOB) AS G_JOB FROM DSN8C10.EMP GROUP BY CUBE (WORKDEPT, JOB) ORDER BY G_DEPT, WORKDEPT, G_JOB, JOB;
Both flags 1 is the grand total. G_DEPT=0 and G_JOB=1 is a department subtotal. Sort on the GROUPING flags if you want totals after detail.
After groups exist, keep or drop them with HAVING. That is Db2 for z/OS aggregate filtering. There is no per-function FILTER (WHERE …) clause like some other SQL dialects; you cannot write SUM(SALARY) FILTER (WHERE JOB = 'CLERK') on z/OS. Instead:
123456SELECT WORKDEPT, SUM(SALARY) AS PAYROLL, COUNT(*) AS N FROM DSN8C10.EMP WHERE JOB <> 'PRES' GROUP BY WORKDEPT HAVING SUM(SALARY) > 200000 AND COUNT(*) >= 3;
Super-groups are groups too. HAVING SUM(SALARY) > 200000 can drop a grand total or keep it independently of detail rows. If you must always print the total, filter with GROUPING() in HAVING (keep rows where GROUPING(WORKDEPT) = 1 OR SUM(SALARY) > 200000) or filter in an outer query.
123456789SELECT DEPT, PAYROLL FROM ( SELECT WORKDEPT AS DEPT, SUM(SALARY) AS PAYROLL, GROUPING(WORKDEPT) AS G FROM DSN8C10.EMP GROUP BY ROLLUP (WORKDEPT) ) AS X WHERE G = 1 OR PAYROLL > 200000;
12345678SELECT YEAR(HIREDATE) AS YR, COUNT(*) AS HIRES, AVG(SALARY) AS AVG_SAL, GROUPING(YEAR(HIREDATE)) AS G_YR FROM DSN8C10.EMP GROUP BY ROLLUP (YEAR(HIREDATE)) HAVING COUNT(*) >= 5 ORDER BY G_YR, YR;
YEAR(HIREDATE) is both the grouping-expression and a select-list item. ROLLUP adds the grand-total row. HAVING COUNT(*) >= 5 applies to year groups and to the total; a company with fewer than five employees would lose the total row as well.
GROUP BY is sorting toys into bins. An expression is labeling bins by year stamped on the toy, not by the toy’s name. ROLLUP also makes a “all years” bin. CUBE makes a bin for every way you can ignore a label. GROUPING() is a sticker that says “this empty label means all toys, not a lost tag.” HAVING is throwing away bins that are too light. You cannot point at “bin number 1” the way you point at “sort by column 2”—you have to say the real label.
1. Can you GROUP BY 1, 2 in Db2 for z/OS the way you ORDER BY 1, 2?
2. ROLLUP(YEAR, MONTH) produces which extra sets beyond (YEAR, MONTH)?
3. What does GROUPING(MONTH) = 1 mean on a result row?
4. How do you group by YEAR(HIREDATE) when the expression cannot sit in GROUP BY?
5. Where do you filter on SUM(SALARY) after grouping?