Cursors and Result Sets

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.

Result Set 101 ALICE 102 BOB 103 CHARLIE Cursor 1. DECLARE 2. OPEN 3. FETCH 4. CLOSE
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:

  1. DECLARE: Define the cursor and the SQL query it will execute.
  2. OPEN: Execute the query and prepare the result set.
  3. FETCH: Retrieve the next row from the result set into host variables.
  4. 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:

cobol βœ“ verified output
       IDENTIFICATION DIVISION.
       PROGRAM-ID. HOSTVARS.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  DCL-EMPLOYEE.
           05 WS-EMP-ID    PIC X(5) VALUE '101  '.
           05 WS-EMP-NAME  PIC X(15) VALUE 'ALICE          '.
       PROCEDURE DIVISION.
           DISPLAY "FETCHED ID: " WS-EMP-ID " NAME: " WS-EMP-NAME.
           STOP RUN.
Output
FETCHED ID: 101   NAME: ALICE          

The Complete Lifecycle in Code

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

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 output cobol

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

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.

  • Test 1 β€” expects "ERROR\n"
Need a hint? (βˆ’25% XP)

Use IF SQLCODE = 0 ... ELSE ...

Show solution (0 XP)
IDENTIFICATION DIVISION.
PROGRAM-ID. CHAL2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01  SQLCA.
    05 SQLCODE PIC S9(9) COMP VALUE -803.
PROCEDURE DIVISION.
    IF SQLCODE = 0
        DISPLAY 'SUCCESS'
    ELSE
        DISPLAY 'ERROR'
    END-IF.
    STOP RUN.