A COBOL program cannot drop a whole query result into a table-valued variable the way some client languages can. With DB2 for z/OS you open a cursor, FETCH rows into host variables, and close the cursor when you are done. This page is the COBOL view: declaration placement, the FETCH loop, WITH HOLD and FOR UPDATE, then rowset (multi-row FETCH) and multi-row INSERT with host-variable arrays.
SELECT INTO is for one row. Zero rows give SQLCODE +100. Two or more rows give -811. As soon as a WHERE clause can match a set, you declare a cursor. Think of the cursor as a bookmark on a result table Db2 built (or is scanning) for your program.
| Step | What it does |
|---|---|
| DECLARE CURSOR | Names the cursor, the SELECT, and options. No database call. |
| OPEN | Runs the query with current host variables; position is before the first row. |
| FETCH | Copies one row or a rowset into host variables / arrays. |
| CLOSE | Releases the result. You can OPEN again with new host-variable values. |
Put DECLARE CURSOR in WORKING-STORAGE (many shops keep all DECLAREs together before PROCEDURE DIVISION). EXEC SQL and END-EXEC each occupy their own source lines. Host variables in the SELECT list and WHERE clause are prefixed with a colon.
123456789101112131415161718192021222324252627282930313233343536WORKING-STORAGE SECTION. EXEC SQL INCLUDE SQLCA END-EXEC. 01 HV-DEPT PIC X(3). 01 HV-EMPNO PIC X(6). 01 HV-LASTNAME PIC X(15). EXEC SQL DECLARE C-EMP CURSOR FOR SELECT EMPNO, LASTNAME FROM DSN8C10.EMP WHERE WORKDEPT = :HV-DEPT ORDER BY EMPNO END-EXEC. PROCEDURE DIVISION. MOVE 'A00' TO HV-DEPT EXEC SQL OPEN C-EMP END-EXEC PERFORM UNTIL SQLCODE NOT = 0 EXEC SQL FETCH C-EMP INTO :HV-EMPNO, :HV-LASTNAME END-EXEC IF SQLCODE = 0 PERFORM 2000-PROCESS-ROW END-IF END-PERFORM IF SQLCODE NOT = +100 PERFORM 9000-SQL-ERROR END-IF EXEC SQL CLOSE C-EMP END-EXEC.
Rules that bite beginners:
If you will UPDATE or DELETE the current row, declare the cursor FOR UPDATE OF column-list (or FOR UPDATE). After a successful FETCH:
12345EXEC SQL UPDATE DSN8C10.EMP SET PHONENO = :HV-PHONE WHERE CURRENT OF C-EMP END-EXEC.
WHERE CURRENT OF names the cursor, not a key predicate. The cursor must be positioned on a row. Read-only SELECT (DISTINCT, join, UNION, FOR FETCH ONLY) cannot be a positioned-update cursor. Ambiguous cursors (Db2 is not sure you will update) can disable blocking and hurt performance — add FOR FETCH ONLY when you will only read.
DECLARE C-EMP CURSOR WITH HOLD FOR ... keeps the cursor open across COMMIT. After commit the position is after the last row you fetched; FETCH next to continue. You cannot positioned-UPDATE until you FETCH again. SQL ROLLBACK closes held cursors. CICS/IMS: do not use SQL COMMIT; WITH HOLD interacts with the transaction manager's syncpoint (see the COMMIT in COBOL page). Held cursors across a CICS pseudo-conversational RETURN do not survive end of task the way beginners hope.
Each FETCH is a trip into Db2. For thousands of rows that overhead adds up, especially through DDF. Rowset processing returns a block of rows in one FETCH.
| Clause | Meaning |
|---|---|
| WITHOUT ROWSET POSITIONING | Default. Single-row FETCH only. FOR n ROWS is invalid on FETCH. |
| WITH ROWSET POSITIONING | Allows FETCH NEXT ROWSET FOR n ROWS and still allows single-row FETCH. |
Host variables become arrays (or a table with OCCURS). The array dimension must be greater than or equal to the number of rows requested on FETCH.
12345678910111213141516171819202122232425262728293001 WS-FETCH-COUNT PIC S9(9) COMP-5 VALUE 100. 01 EMP-ROWSET. 05 HV-EMPNO-ARR PIC X(6) OCCURS 100 TIMES. 05 HV-LNAME-ARR PIC X(15) OCCURS 100 TIMES. 05 IND-LNAME-ARR PIC S9(4) COMP-5 OCCURS 100 TIMES. EXEC SQL DECLARE C-BULK CURSOR WITH ROWSET POSITIONING FOR SELECT EMPNO, LASTNAME FROM DSN8C10.EMP WHERE WORKDEPT = :HV-DEPT ORDER BY EMPNO END-EXEC. EXEC SQL OPEN C-BULK END-EXEC. PERFORM UNTIL SQLCODE = +100 EXEC SQL FETCH NEXT ROWSET FROM C-BULK FOR :WS-FETCH-COUNT ROWS INTO :HV-EMPNO-ARR, :HV-LNAME-ARR :IND-LNAME-ARR END-EXEC IF SQLCODE = 0 OR SQLCODE = +100 PERFORM 2000-PROCESS-ROWSET ELSE PERFORM 9000-SQL-ERROR END-IF END-PERFORM.
On a rowset FETCH, SQLCODE +100 can mean you reached the end and the last arrays still hold a partial set. Read SQLERRD(3) (or GET DIAGNOSTICS ROW_COUNT) and process that many occurrences. Do not assume 100 rows just because you asked for 100. FOR n ROWS accepts a host variable or integer; the documented maximum is 32767.
FETCH FIRST n ROWS ONLY on the SELECT limits the result table. FOR n ROWS on FETCH limits how many rows this call copies. They are not the same clause. Mixing FETCH NEXT (one row) and FETCH NEXT ROWSET in one loop shifts the current position in ways that surprise people: after rows 1–10 as a rowset, a single FETCH NEXT is row 2 of the next positioning, not “skip a block.”
Positioned UPDATE/DELETE on a rowset cursor: WHERE CURRENT OF updates or deletes the entire current rowset. FOR ROW n OF ROWSET targets one row inside that rowset.
The same arrays can feed INSERT. One SQL statement inserts many rows:
123456EXEC SQL INSERT INTO HR.DAILY_LOG (EMPNO, LOG_DATE, MSG) VALUES (:HV-EMPNO-ARR, :HV-DATE-ARR, :HV-MSG-ARR) FOR :WS-INSERT-COUNT ROWS END-EXEC.
Multi-row INSERT is not a cursor, but shops learn it next to rowset FETCH because both need host-variable arrays and both exist to cut SQL call volume. Load utilities still win for huge conversions; use multi-row INSERT for application-sized batches.
A cursor is a bookmark in a picture book. OPEN is opening the book to the page that matches your search. FETCH is looking at one picture and copying it onto your paper. When there are no pictures left, the librarian says “that’s all” (+100). CLOSE is putting the book back. A rowset FETCH is like photocopying a whole handful of pages at once so you do not walk to the desk a hundred times. Multi-row INSERT is handing the librarian a stack of new pages in one trip instead of one page per trip.
1. When are host variables in a cursor SELECT evaluated?
2. What does SQLCODE +100 mean on FETCH?
3. WITH ROWSET POSITIONING is required when you:
4. FOR n ROWS on INSERT means:
5. COMMIT closes which cursors?