The keyword WITH shows up in three different DB2 for z/OS places that beginners mash together: common table expressions (including recursive queries), FETCH FIRST … WITH TIES, and statement isolation (WITH UR, CS, RS, RR). This page separates those meanings and then walks a department-tree recursive CTE the way IBM documents bill-of-materials style SQL.
| Spelling | Role |
|---|---|
| WITH name AS (query) | Common table expression (CTE) |
| FETCH FIRST n ROWS WITH TIES | Keep sort-key ties when limiting rows |
| WITH UR / CS / RS / RR | Statement isolation (locks / dirty read) |
You can use more than one in the same statement: a CTE at the front, ORDER BY and FETCH FIRST WITH TIES in the middle, and WITH CS at the end. Read them as separate clauses, not as one “WITH block.”
A CTE names a fullselect for the rest of the statement. It is not a created view and not a declared global temporary table. It lives only for that SQL statement. Use it to avoid repeating a subquery, to name an intermediate result, or to split a hard query into readable steps.
12345678910WITH DEPTPAY AS ( SELECT WORKDEPT, AVG(SALARY) AS AVGSAL, COUNT(*) AS EMPCNT FROM DSN8C10.EMP GROUP BY WORKDEPT ) SELECT D.DEPTNO, D.DEPTNAME, P.AVGSAL, P.EMPCNT FROM DSN8C10.DEPT D INNER JOIN DEPTPAY P ON P.WORKDEPT = D.DEPTNO WHERE P.AVGSAL > 50000;
Rules of thumb:
If a fullselect in the CTE refers to the CTE’s own name, the CTE is recursive. Queries using recursion support bill of materials, org charts, and network walks. The standard shape is:
12345678910111213WITH RPL (LEVEL, DEPTNO, DEPTNAME, ADMRDEPT) AS ( SELECT 1, DEPTNO, DEPTNAME, ADMRDEPT FROM DSN8C10.DEPT WHERE DEPTNO = 'A00' UNION ALL SELECT RPL.LEVEL + 1, D.DEPTNO, D.DEPTNAME, D.ADMRDEPT FROM RPL, DSN8C10.DEPT D WHERE D.ADMRDEPT = RPL.DEPTNO AND D.DEPTNO <> D.ADMRDEPT ) SELECT LEVEL, DEPTNO, DEPTNAME, ADMRDEPT FROM RPL ORDER BY LEVEL, DEPTNO;
The seed is department A00. Each iteration attaches departments whose ADMRDEPT equals the previous level’s DEPTNO. LEVEL counts depth. The extra predicate DEPTNO <> ADMRDEPT avoids a row that points at itself looping forever.
Practical guards:
Recursion is not a substitute for a numbers table when you only need 1..N. It is the right tool when each row’s children are found by joining back to the same table.
WITH TIES belongs to the fetch-first clause, not to CTEs. It changes what “first n rows” means when ORDER BY has ties at the cutoff.
1234SELECT WORKDEPT, LASTNAME, SALARY FROM DSN8C10.EMP ORDER BY SALARY DESC FETCH FIRST 5 ROWS WITH TIES;
FETCH FIRST 5 ROWS ONLY returns at most five rows. If the fifth and sixth employees share the same salary, ONLY can split that tie. WITH TIES keeps every row whose ORDER BY key matches the last included row, so you might get more than five rows. You need ORDER BY for WITH TIES to be meaningful. This is a Db2 12 pagination feature (application compatibility / function level permitting).
A SELECT can end with an isolation-clause that overrides the package bind option for that statement only.
| Clause | Name | Meaning |
|---|---|---|
| WITH UR | Uncommitted read | Can read uncommitted changes; fewest read locks |
| WITH CS | Cursor stability | Current fetched row protected; typical default bind |
| WITH RS | Read stability | Qualifying rows stay stable; phantoms possible |
| WITH RR | Repeatable read | Strongest; repeat the query, same rows, no phantoms |
| (omit WITH) | Implicit isolation | Package/plan ISOLATION bind option applies |
123456789SELECT EMPNO, LASTNAME, SALARY FROM DSN8C10.EMP WHERE WORKDEPT = 'A00' WITH UR; SELECT EMPNO, LASTNAME FROM DSN8C10.EMP WHERE EMPNO = '000010' WITH RR;
Isolation is not a CTE. Putting WITH UR next to WITH mycte AS (…) is two clauses in one statement, not one keyword with two meanings. BIND still matters: a package bound UR already reads dirty unless a statement raises isolation with WITH CS/RS/RR.
Related read-only hints: FOR READ ONLY (or FOR FETCH ONLY) tells Db2 you will not use positioned UPDATE/DELETE on the cursor. Isolation and FOR READ ONLY solve different problems—locks versus cursor intent.
A CTE is a nickname for a pile of blocks you built so you can point at that pile twice without rebuilding it in the sentence. Recursive SQL is stacking blocks: start with the floor (seed), then keep stacking the next block that sits on the last one (UNION ALL) until no block fits. WITH TIES is “if two kids tied for fifth place, both get a ribbon.” WITH UR is “peek at the other table before they finish writing”—sometimes you see a scribble that gets erased.
A CTE that references itself is recursive. Some products require the RECURSIVE keyword; on Db2 for z/OS the self-reference plus UNION ALL is the documented pattern. Follow your SQL Reference for your function level if RECURSIVE is accepted as optional syntax.
Keep the iterative member to a single reference of the CTE joined to a base table. Fancy multiple self-references are restricted. If you need two hierarchies, use two CTEs.
No. UR is about read isolation, not about whether Db2 logs updates. It does not make INSERT unlogged.
1. What does a non-recursive CTE do?
2. How is a recursive CTE wired on Db2 for z/OS?
3. What does WITH UR on a SELECT mean?
4. What does FETCH FIRST n ROWS WITH TIES add?
5. Must recursive members use UNION ALL?