COBOL GnuCOBOL 3.1.2 (Illustrative for DB2) Β·
β verified by execution on 2026-08-16
When you write embedded SQL in a COBOL program, a simple SELECT INTO statement works perfectly if you are absolutely certain the query will return exactly one row. However, most real-world database queries return multiple rowsβa list of employees, a batch of transactions, or a catalog of products. COBOL cannot process an entire set of rows in a single operation.
To bridge this gap, DB2 uses cursors. A cursor acts as a pointer that navigates through the result set of a query, allowing your COBOL program to fetch and process rows one at a time.
A cursor points to the current row in a result set and advances with each FETCH.
The Cursor Lifecycle
Working with cursors in COBOL involves four distinct steps:
DECLARE: Define the cursor and the SQL query it will execute.
OPEN: Execute the query and prepare the result set.
FETCH: Retrieve the next row from the result set into host variables.
CLOSE: Release the resources held by the cursor.
(Note: The DB2 examples in this lesson are illustrative. DB2 requires a precompiler that transforms EXEC SQL statements into native calls, which standard compilers like GnuCOBOL cannot process directly.)
Host Variables
Before working with cursors, your program must have memory locations prepared to receive the data. These are called host variables. They are standard COBOL data items:
You must first declare the cursor to tie a name to a specific SELECT statement. Then you OPEN the cursor to execute the query, FETCH rows one by one, and finally CLOSE the cursor to free resources.
EXEC SQL DECLARE EMP-CURSOR CURSOR FOR SELECT EMP_ID, EMP_NAME FROM EMPLOYEE END-EXEC. EXEC SQL OPEN EMP-CURSOR END-EXEC. EXEC SQL FETCH EMP-CURSOR INTO :WS-EMP-ID, :WS-EMP-NAME END-EXEC. EXEC SQL CLOSE EMP-CURSOR END-EXEC.
This cannot run here β EXEC SQL needs a DB2 precompiler and a live database, neither of
which exists outside a mainframe, so there is no output block to show. Read the four verbs in
order: DECLARE names the query without executing it, OPEN runs it and positions before the
first row, FETCH pulls one row into your host variables, CLOSE releases the result set.
By default, cursors move forward only. If you need to move backward or jump to specific rows, you can define a scrollable cursor by using the SCROLL keyword (e.g., DECLARE C1 SCROLL CURSOR FOR...).
Evaluating SQLCODE Natively
How do you know if a FETCH worked? The database updates the SQLCODE field in the SQLCA communication block. When a FETCH successfully retrieves a row, SQLCODE is 0. When the result set is exhausted (no more rows to fetch), SQLCODE becomes 100. If an error occurs, it is negative.
Here is a standard COBOL program that simulates checking the SQLCODE using an EVALUATE statement after an operation:
cobolβ verified output
IDENTIFICATION DIVISION. PROGRAM-ID. EVALSQL. DATA DIVISION. WORKING-STORAGE SECTION. 01 SQLCODE PIC S9(9) COMP VALUE -803. PROCEDURE DIVISION. EVALUATE SQLCODE WHEN 0 DISPLAY "SUCCESS" WHEN 100 DISPLAY "NOT FOUND" WHEN OTHER DISPLAY "ERROR: " SQLCODE END-EVALUATE. STOP RUN.
Output
ERROR: -000000803
Your output
The Fetch Loop
The most common pattern for processing cursors is a PERFORM UNTIL loop. How does the loop know when it has reached the end of the result set? It checks the SQLCODE field within the SQLCA.
When a FETCH statement successfully retrieves a row, SQLCODE is 0. When the result set is exhausted (there are no more rows to fetch), SQLCODE becomes 100. If an error occurs, SQLCODE is negative (e.g., -818).
PERFORM UNTIL SQLCODE = 100 OR SQLCODE < 0 EXEC SQL FETCH EMP-CURSOR INTO :WS-EMP-ID, :WS-EMP-NAME END-EXEC IF SQLCODE = 0 DISPLAY 'FETCHED: ' WS-EMP-NAME END-IF END-PERFORM.
Run against an EMPLOYEE table holding Alice, Bob and Charlie, that loop would print one
FETCHED: line per row and then stop when SQLCODE turns 100. Those three names are an
illustration, not captured output β as above, nothing here was executed.
Common Misconceptions
A frequent point of confusion is thinking that a cursor loads the entire result set into your COBOL programβs memory. A cursor is just a pointer. It manages state on the database server side and only transmits exactly one row to your COBOL memory per FETCH call.
Another misconception is that you can update the row currently pointed to by any cursor. If you intend to update rows while fetching, you must explicitly append FOR UPDATE to the DECLARE CURSOR statement.
To understand the fetch loop mechanics natively in COBOL, predict the output of this simulated cursor loop:
Predict the outputcobol
Read the code. What exactly will it print? Commit to an answer before you look.
IDENTIFICATION DIVISION. PROGRAM-ID. SIMCURS. DATA DIVISION. WORKING-STORAGE SECTION. 01 SQLCODE PIC S9(9) COMP VALUE 0. 01 F-COUNT PIC 9 VALUE 0. PROCEDURE DIVISION. PERFORM UNTIL SQLCODE = 100 ADD 1 TO F-COUNT IF F-COUNT > 2 MOVE 100 TO SQLCODE ELSE DISPLAY 'ROW ' F-COUNT END-IF END-PERFORM DISPLAY 'DONE' STOP RUN.
Output
ROW 1
ROW 2
DONE
You predicted
Understanding how to control cursor flow using SQLCODE is vital for correctly processing data in DB2 COBOL applications.
Check yourself
What happens when you execute a FETCH statement on a cursor?
Reveal answer
It retrieves exactly one row and advances the cursor. β A row-positioned FETCH retrieves exactly one row per call, advancing the cursor pointer to the next row.
Which statement is true about explicitly closing a cursor?
Reveal answer
It frees resources immediately, though a commit may also close it. β While the end of a program or a commit may close them, explicitly closing a cursor frees its resources immediately.
What does a cursor fundamentally represent in memory?
Reveal answer
A pointer to the current row in the result set. β A cursor is just a pointer to the current row in the result set, not a copy of all the data.
Can any cursor be used to update rows?
Reveal answer
No, updates require the cursor to be declared FOR UPDATE. β Updating rows through a cursor requires declaring it with the FOR UPDATE clause.
Challenges
Challenge 1 +15 XP
Write a COBOL IF statement to display 'END OF DATA' when SQLCODE equals 100.
cobolβ solution verified at build time
COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the
fixed code should be, then open the solution and compare. The expected output
below is real β it came from compiling the solution with GnuCOBOL when this page
was built.
Test 1 β expects "END OF DATA\n"
Need a hint? (β25% XP)
Use IF SQLCODE = 100.
Show solution (0 XP)
IDENTIFICATION DIVISION.PROGRAM-ID. CHAL1.DATA DIVISION.WORKING-STORAGE SECTION.01 SQLCA. 05 SQLCODE PIC S9(9) COMP VALUE 100.PROCEDURE DIVISION. IF SQLCODE = 100 DISPLAY 'END OF DATA' END-IF. STOP RUN.
π Bug Hunt+15 XP
This program simulates fetching from a DB2 cursor but attempts to check for a successful fetch by seeing if SQLCODE is > 0. Run the code to see it fail, then fix the IF statement so it only displays 'SUCCESS' when SQLCODE is exactly 0.
This code runs. It just does the wrong thing. Read it, find the defect,
fix it β the tests below decide when you are right.
cobolβ solution verified at build time
COBOL has no in-browser runtime, so this one is pen-and-paper: work out what the
fixed code should be, then open the solution and compare. The expected output
below is real β it came from compiling the solution with GnuCOBOL when this page
was built.