DB2 COBOL Embedded SQL: Complete Guide with Examples

DB2 COBOL Embedded SQL: Complete Guide
In the mainframe world, the dominant pattern for accessing DB2 is embedded SQL in COBOL — SQL statements written directly inside COBOL source code. This is how millions of lines of production banking, insurance, and government code interact with DB2 every day. Understanding how embedded SQL works, how to handle results and errors, and how the compile/precompile/bind workflow fits together is essential for any mainframe developer.
How Embedded SQL Works
Embedded SQL in COBOL follows a well-defined compilation pipeline:
COBOL Source (with EXEC SQL blocks)
↓
DB2 Precompiler (DSNHPC)
↓
Modified COBOL Source + DBRM (Database Request Module)
↓
COBOL Compiler (IGYCRCTL) BIND (DSNHBND)
↓ ↓
Object Module DB2 Package/Plan (in Catalog)
↓
Link-Edit (IEWL)
↓
Load Module
↓
Execute (program + DB2 plan cooperate at runtime)At runtime, when the program reaches an EXEC SQL statement, control passes to DB2, which executes the precompiled access plan and returns results to the host variables.
The SQLCA — SQL Communication Area
Before writing any SQL, include the SQL Communication Area in your WORKING-STORAGE SECTION:
WORKING-STORAGE SECTION.
EXEC SQL
INCLUDE SQLCA
END-EXEC.The SQLCA is a set of fields DB2 populates after every SQL statement. The most important fields:
| Field | Type | Meaning |
|---|---|---|
| SQLCODE | PIC S9(9) COMP | 0=success, +100=not found, negative=error |
| SQLSTATE | PIC X(5) | ANSI standard 5-char state code |
| SQLERRM | VARCHAR 70 | Error message text |
| SQLERRD(3) | PIC S9(9) COMP | Row count affected by last DML |
After every EXEC SQL block, test SQLCODE:
EVALUATE SQLCODE
WHEN 0
PERFORM 1000-PROCESS-ROW
WHEN +100
MOVE 'Y' TO WS-EOF-FLAG
WHEN OTHER
PERFORM 9900-SQL-ERROR
END-EVALUATE.Host Variables
Host variables are COBOL data items used to pass values to and receive values from SQL statements. They are prefixed with a colon (:) inside EXEC SQL blocks:
WORKING-STORAGE SECTION.
01 WS-EMPNO PIC X(6).
01 WS-LASTNAME PIC X(15).
01 WS-SALARY PIC S9(7)V99 COMP-3.
01 WS-WORKDEPT PIC X(3).
PROCEDURE DIVISION.
1000-FETCH-EMPLOYEE.
MOVE '000010' TO WS-EMPNO
EXEC SQL
SELECT LASTNAME, SALARY, WORKDEPT
INTO :WS-LASTNAME, :WS-SALARY, :WS-WORKDEPT
FROM EMPLOYEE
WHERE EMPNO = :WS-EMPNO
END-EXEC
EVALUATE SQLCODE
WHEN 0
DISPLAY 'Found: ' WS-LASTNAME
WHEN +100
DISPLAY 'Employee not found'
WHEN OTHER
PERFORM 9900-SQL-ERROR
END-EVALUATE.NULL Indicator Variables
When a DB2 column can contain NULL, you must provide a null indicator variable alongside the host variable:
01 WS-BONUS PIC S9(7)V99 COMP-3.
01 WS-BONUS-IND PIC S9(4) COMP. * -1=NULL, 0=not null
EXEC SQL
SELECT BONUS
INTO :WS-BONUS :WS-BONUS-IND
FROM EMPLOYEE
WHERE EMPNO = :WS-EMPNO
END-EXEC
IF WS-BONUS-IND = -1
DISPLAY 'No bonus assigned'
ELSE
DISPLAY 'Bonus: ' WS-BONUS
END-IF.A null indicator value of -1 means the column was NULL. 0 means not NULL. Positive values indicate truncation (for character data).
Singleton SELECT — One Row
Use singleton SELECT when your WHERE clause guarantees exactly one row:
1000-GET-DEPT-NAME.
EXEC SQL
SELECT DEPTNAME, MGRNO
INTO :WS-DEPTNAME, :WS-MGRNO :WS-MGRNO-IND
FROM DEPARTMENT
WHERE DEPTNO = :WS-DEPT
END-EXEC
IF SQLCODE = +100
MOVE 'UNKNOWN' TO WS-DEPTNAME
ELSE IF SQLCODE NOT = 0
PERFORM 9900-SQL-ERROR
END-IF.If the SELECT returns more than one row, DB2 raises SQLCODE -811 (multiple rows returned for singleton SELECT). Use a cursor when multiple rows are expected.
Cursors — Processing Multiple Rows
A cursor is a named pointer into a result set. You DECLARE it, OPEN it (which executes the query), FETCH rows one at a time in a loop, and CLOSE it when done.
Step 1 — DECLARE the Cursor
WORKING-STORAGE SECTION.
EXEC SQL
DECLARE EMP-CURSOR CURSOR FOR
SELECT EMPNO, LASTNAME, SALARY, WORKDEPT
FROM EMPLOYEE
WHERE WORKDEPT = :WS-DEPT
ORDER BY SALARY DESC
END-EXEC.The DECLARE is a declarative — it does not execute the query. Place it in WORKING-STORAGE or after the PROCEDURE DIVISION header.
Step 2 — OPEN the Cursor
2000-OPEN-CURSOR.
MOVE 'A00' TO WS-DEPT
EXEC SQL
OPEN EMP-CURSOR
END-EXEC
IF SQLCODE NOT = 0
PERFORM 9900-SQL-ERROR
END-IF.OPEN executes the SELECT and positions the cursor before the first row.
Step 3 — FETCH in a Loop
2100-FETCH-LOOP.
PERFORM UNTIL WS-EOF = 'Y'
EXEC SQL
FETCH EMP-CURSOR
INTO :WS-EMPNO,
:WS-LASTNAME,
:WS-SALARY,
:WS-WORKDEPT
END-EXEC
EVALUATE SQLCODE
WHEN 0
PERFORM 2200-PROCESS-EMPLOYEE
WHEN +100
MOVE 'Y' TO WS-EOF
WHEN OTHER
PERFORM 9900-SQL-ERROR
END-EVALUATE
END-PERFORM.Step 4 — CLOSE the Cursor
2300-CLOSE-CURSOR.
EXEC SQL
CLOSE EMP-CURSOR
END-EXEC.Always CLOSE cursors when done — open cursors consume DB2 thread resources.
INSERT, UPDATE, DELETE in COBOL
DML statements use host variables for values:
3000-INSERT-EMPLOYEE.
EXEC SQL
INSERT INTO EMPLOYEE
(EMPNO, FIRSTNME, MIDINIT, LASTNAME,
WORKDEPT, EDLEVEL, SEX, SALARY)
VALUES
(:WS-EMPNO, :WS-FIRSTNME, :WS-MIDINIT, :WS-LASTNAME,
:WS-WORKDEPT, :WS-EDLEVEL, :WS-SEX, :WS-SALARY)
END-EXEC
IF SQLCODE = 0
EXEC SQL COMMIT END-EXEC
ELSE IF SQLCODE = -803
DISPLAY 'Duplicate employee number: ' WS-EMPNO
EXEC SQL ROLLBACK END-EXEC
ELSE
PERFORM 9900-SQL-ERROR
EXEC SQL ROLLBACK END-EXEC
END-IF.
3100-UPDATE-SALARY.
EXEC SQL
UPDATE EMPLOYEE
SET SALARY = :WS-NEW-SALARY
WHERE EMPNO = :WS-EMPNO
END-EXEC
IF SQLCODE = 0
DISPLAY 'Updated ' SQLERRD(3) ' rows'
ELSE
PERFORM 9900-SQL-ERROR
END-IF.SQLERRD(3) gives the number of rows affected by the last INSERT/UPDATE/DELETE.
Commit and Rollback
Transaction control in embedded SQL COBOL:
EXEC SQL COMMIT END-EXEC. * Commit all changes since last commit
EXEC SQL ROLLBACK END-EXEC. * Undo all changes since last commitIn batch programs, commit frequently enough to avoid holding locks for extended periods. A common pattern is to commit every N records (e.g., every 1,000 rows processed).
Error Handling Routine
A standard SQL error routine:
9900-SQL-ERROR.
DISPLAY '*** DB2 ERROR ***'
DISPLAY 'SQLCODE : ' SQLCODE
DISPLAY 'SQLSTATE: ' SQLSTATE
DISPLAY 'SQLERRM : ' SQLERRM
EXEC SQL ROLLBACK END-EXEC
MOVE 8 TO RETURN-CODE
STOP RUN.The JCL Compile-Precompile-Bind Workflow
A typical three-step JCL procedure for COBOL DB2 programs:
//STEP1 EXEC PGM=DSNHPC,PARM='HOST(COBOL),SOURCE' * DB2 Precompile
//DBRMLIB DD DSN=MY.DBRMLIB(EMPRPT),DISP=SHR
//SYSCIN DD DSN=&&MODCBL,DISP=(NEW,PASS)
//SYSIN DD DSN=MY.SOURCE(EMPRPT),DISP=SHR
//STEP2 EXEC PGM=IGYCRCTL,PARM='RENT,NODECK,OBJECT' * COBOL Compile
//SYSLIN DD DSN=&&OBJ,DISP=(NEW,PASS)
//SYSIN DD DSN=&&MODCBL,DISP=(OLD,DELETE)
//STEP3 EXEC PGM=IEWL,PARM='RENT,LIST' * Link-Edit
//SYSLIB DD DSN=DSN.SDSNLOAD,DISP=SHR
//SYSLIN DD DSN=&&OBJ,DISP=(OLD,DELETE)
//SYSLMOD DD DSN=MY.LOADLIB(EMPRPT),DISP=SHR
//STEP4 EXEC PGM=IKJEFT01 * BIND
//SYSTSIN DD *
DSN SYSTEM(DSN1)
BIND PACKAGE(EMPPKG) MEMBER(EMPRPT) ACTION(REPLACE) ISOLATION(CS)
END
/*Next Steps
With DB2 embedded SQL in COBOL mastered, you are ready for online transaction processing. Read the DB2 CICS Transactions guide to learn how CICS programs access DB2 in an OLTP environment. For performance tuning your SQL, see the DB2 EXPLAIN and Optimization guide. Full learning path at the DB2 Mastery course hub.
