Chapter 7 Exercises
Multi-Row Operations
Exercise 1: Basic Multi-Row FETCH Conversion
Convert the following single-row FETCH program to use multi-row FETCH with a rowset size of 200. Include indicator arrays for all columns, proper SQLERRD(3) checking, and end-of-data handling.
EXEC SQL
DECLARE CSR-CUST CURSOR FOR
SELECT CUST_ID, CUST_NAME, CUST_STATUS, CREDIT_LIMIT
FROM CUSTOMER_MASTER
WHERE REGION_CODE = :WS-REGION
ORDER BY CUST_ID
END-EXEC.
EXEC SQL OPEN CSR-CUST END-EXEC.
PERFORM UNTIL SQLCODE = 100
EXEC SQL
FETCH CSR-CUST
INTO :WS-CUST-ID, :WS-CUST-NAME,
:WS-CUST-STATUS, :WS-CREDIT-LIMIT
END-EXEC
IF SQLCODE = 0
PERFORM PROCESS-CUSTOMER
END-IF
END-PERFORM.
Deliverable: Complete WORKING-STORAGE and PROCEDURE DIVISION code for the multi-row version.
Exercise 2: Multi-Row INSERT with Accumulate-and-Flush
Write a COBOL program fragment that accumulates transaction log records into a 300-row array and flushes them using multi-row INSERT. The log table has columns: LOG_ID (CHAR(20)), LOG_TSTAMP (TIMESTAMP), LOG_TYPE (CHAR(3)), LOG_MSG (VARCHAR(200)), PROGRAM_ID (CHAR(8)).
Requirements: - Use NOT ATOMIC CONTINUE ON SQLEXCEPTION (logging should never abort the batch) - Track total rows inserted vs. total rows attempted - Handle the final flush after the main loop ends - Include error reporting when SQLCODE is not 0 or 100
Exercise 3: Rowset Size Analysis
CNB processes a table with rows averaging 450 bytes wide, with 8 columns (3 nullable). The program runs in a batch region with 256 MB virtual storage, of which approximately 180 MB is available after load module, DB2 interface, and other allocations.
Calculate the WORKING-STORAGE requirements for rowset sizes of 100, 500, 1000, and 2000. For each, compute: - Data array size (rowset * row_width) - Indicator array size (rowset * 2 bytes * 8 columns) - Total storage - Percentage of available region
Recommend a rowset size and justify your answer.
Exercise 4: Multi-Row FETCH with Control Break
Modify the multi-row FETCH pattern to handle a control break on BRANCH_CODE. The program must produce a branch total line whenever BRANCH_CODE changes. Remember that with multi-row FETCH, the control break can occur anywhere within a rowset — you must check every row, not just the first and last.
Challenge: What happens when a control break falls between the last row of one rowset and the first row of the next? Write the logic to handle this edge case.
Exercise 5: Atomic vs. Non-Atomic Decision Matrix
For each of the following scenarios, state whether you would use ATOMIC or NOT ATOMIC multi-row INSERT, and explain why:
a) Inserting daily posted transactions to a financial ledger table b) Writing diagnostic trace records during batch processing c) Inserting new customer records from a daily enrollment file d) Writing MQ message confirmations to a tracking table e) Inserting regulatory reporting records for OCC submission f) Writing performance statistics records for a monitoring dashboard
MERGE Operations
Exercise 6: Basic MERGE Implementation
Write a MERGE statement that maintains the PRODUCT_INVENTORY table from a daily shipment feed. The shipment feed is loaded into a global temporary table SESSION.DAILY_SHIPMENTS.
- If the product exists: UPDATE quantity by adding shipment quantity, update LAST_RECEIPT_DATE
- If the product doesn't exist: INSERT with shipment quantity as initial stock, current date as both FIRST_RECEIPT_DATE and LAST_RECEIPT_DATE
- Include the COBOL host variable declarations and the EXEC SQL block
Exercise 7: MERGE with Conditional Logic
Extend Exercise 6 so that the UPDATE branch behaves differently based on the shipment type: - If SHIPMENT_TYPE = 'NEW': Add to quantity - If SHIPMENT_TYPE = 'RETURN': Subtract from quantity - If SHIPMENT_TYPE = 'REPLACE': Set quantity to shipment quantity
Write the MERGE statement using CASE expressions in the UPDATE SET clause.
Exercise 8: MERGE Error Analysis
A developer writes the following MERGE and gets SQLCODE -788 in production. Diagnose the problem and write the corrected solution.
MERGE INTO ACCOUNT_SUMMARY AS TGT
USING DAILY_TRANSACTIONS AS SRC
ON TGT.ACCT_NUM = SRC.ACCT_NUM
WHEN MATCHED THEN
UPDATE SET BALANCE = TGT.BALANCE + SRC.TXN_AMT
WHEN NOT MATCHED THEN
INSERT (ACCT_NUM, BALANCE)
VALUES (SRC.ACCT_NUM, SRC.TXN_AMT);
The DAILY_TRANSACTIONS table contains multiple transactions per account per day.
Exercise 9: MERGE Combined with Multi-Row Source
Write the complete COBOL pattern for: 1. Multi-row INSERT 500 rows into a declared global temporary table 2. MERGE from the temporary table into a target table 3. DELETE the temporary table contents 4. Handle all SQLCODEs
Include the DECLARE GLOBAL TEMPORARY TABLE DDL.
Temporal Tables
Exercise 10: System-Period Temporal Table DDL
Write the complete DDL to create a system-period temporal table for EMPLOYEE_SALARY with columns: EMP_ID (CHAR(8)), SALARY (DECIMAL(11,2)), GRADE_LEVEL (CHAR(3)), EFFECTIVE_DATE (DATE), DEPT_CODE (CHAR(6)). Include the history table and the ALTER TABLE to enable versioning. Place the tables in tablespace DBHR.TSEMPSAL and DBHR.TSEMPHIST.
Exercise 11: Temporal Query Scenarios
Write SQL queries for each scenario using the EMPLOYEE_SALARY temporal table from Exercise 10:
a) What was employee E0048291's salary on January 15, 2025? b) Show all salary changes for employee E0048291 between January 1, 2024 and December 31, 2025. c) Find all employees whose salary was above $150,000 at any point during Q3 2025. d) Compare employee E0048291's current salary to their salary exactly one year ago (use both a temporal query and a current query, then compute the difference in COBOL).
Exercise 12: Application-Period Temporal Table
Design an application-period temporal table for insurance policy coverage. A policy can have different coverage levels during different time periods (e.g., basic coverage Jan–Jun, premium coverage Jul–Dec). Write the DDL and a query that finds what coverage a policy holder had on a specific date.
Exercise 13: Bi-Temporal Query
Using the CLAIMS_COVERAGE bi-temporal table from Section 7.4, write a query that answers: "According to what our system knew on March 1, 2025, what was patient P003928471's coverage during February 2025? Now compare that to what we know today about that same February period."
Write both queries and explain what it means if they return different results.
Exercise 14: History Table Purge Job
Write the SQL for a batch job that purges temporal history records older than 7 years from BENEFICIARY_STATUS_HIST. Consider:
- The purge must not interfere with the versioning mechanism
- Use DELETE with a WHERE clause on SYS_END (why SYS_END and not SYS_START?)
- Calculate expected I/O impact if the history table has 500 million rows and 30% are older than 7 years
Recursive CTEs
Exercise 15: Basic Org Chart Traversal
Write a recursive CTE that produces a complete organizational chart from the EMPLOYEE_DIRECTORY table (columns: EMP_ID, EMP_NAME, TITLE, DEPT_CODE, MGR_ID). The output should include:
- Employee hierarchy level (CEO = 1)
- Full management path (e.g., "E001 > E015 > E089 > E234")
- Direct report count for each manager (hint: use a second CTE or subquery)
- Depth limit of 12 levels
Exercise 16: Account Hierarchy with Aggregation
CNB has a MASTER_ACCOUNT / SUB_ACCOUNT hierarchy up to 8 levels deep. Write a recursive CTE that: - Starts from a given master account - Traverses all sub-accounts - Aggregates the total balance across all levels - Returns each account with its own balance and the sum of all its descendants' balances
Table: ACCOUNT_STRUCTURE (ACCT_NUM, PARENT_ACCT, ACCT_NAME, ACCT_TYPE, BALANCE)
Exercise 17: Cycle Detection
Given the following data in ACCOUNT_STRUCTURE, determine which rows create a cycle and write a recursive CTE with cycle detection that handles this gracefully:
| ACCT_NUM | PARENT_ACCT |
|---|---|
| A001 | NULL |
| A002 | A001 |
| A003 | A002 |
| A004 | A003 |
| A005 | A004 |
| A003 | A005 |
Write the CTE and explain what would happen without cycle detection.
Exercise 18: Regulatory Reporting Hierarchy
Federal Benefits has a reporting structure where benefit programs roll up through regional offices to state offices to federal divisions. Write a recursive CTE that: - Starts from a specific benefit program - Traverses up to the federal division - Collects the total beneficiary count and total disbursement amount at each level - Returns the complete rollup path
Table: REPORTING_HIERARCHY (UNIT_ID, PARENT_UNIT_ID, UNIT_NAME, UNIT_TYPE, BENEFICIARY_COUNT, DISBURSEMENT_AMT)
OLAP Functions
Exercise 19: Running Total Replacement
Rewrite the following COBOL cursor-loop logic as a single SQL query using OLAP functions:
MOVE SPACES TO WS-PREV-ACCT.
MOVE 0 TO WS-RUNNING-BAL.
MOVE 0 TO WS-TXN-COUNT.
PERFORM UNTIL SQLCODE = 100
FETCH CSR-TXN INTO :WS-ACCT, :WS-AMT, :WS-DATE
IF WS-ACCT NOT = WS-PREV-ACCT
IF WS-PREV-ACCT NOT = SPACES
PERFORM WRITE-ACCOUNT-SUMMARY
END-IF
MOVE 0 TO WS-RUNNING-BAL
MOVE 0 TO WS-TXN-COUNT
MOVE WS-ACCT TO WS-PREV-ACCT
END-IF
ADD WS-AMT TO WS-RUNNING-BAL
ADD 1 TO WS-TXN-COUNT
END-PERFORM.
Your SQL must produce: ACCT_NUM, TXN_DATE, TXN_AMT, RUNNING_BALANCE, TXN_COUNT_SO_FAR.
Exercise 20: LAG/LEAD for Change Detection
Write an SQL query that identifies transactions where the amount differs from the previous transaction by more than 200% (potential fraud indicator). Use LAG() to access the previous transaction amount. Include: - The account number - Current and previous transaction amounts - The percentage change - A flag column ('ALERT' or 'NORMAL')
Exercise 21: RANK for Top-N per Group
Write an SQL query that finds the top 3 highest-value transactions per branch per day. Use RANK() or DENSE_RANK() and explain your choice. The result should include only the top-3 ranked transactions (use a CTE with the ranking, then filter).
Exercise 22: Moving Average
Write an OLAP function query that computes a 7-day moving average of transaction amounts per account. Use RANGE BETWEEN to handle days with no transactions correctly (explain why ROWS BETWEEN would give incorrect results for this use case).
Exercise 23: Multiple Window Functions
Write a single SQL query that produces a daily branch performance report with: - Branch total for the day (SUM with PARTITION BY branch) - Running daily total across all branches (SUM with ORDER BY date) - Branch rank by daily volume (RANK with PARTITION BY date, ORDER BY sum) - Previous day's branch total (LAG on the branch daily sum) - Percent change from previous day
This requires nested OLAP functions or CTEs with OLAP functions. Design the approach and write the SQL.
Decision Framework and Integration
Exercise 24: Set-Based vs. Cursor-Based Analysis
For each of the following batch program requirements, classify as set-based, cursor-based, or hybrid. Justify your choice.
a) Calculate interest on 2 million savings accounts using a tiered rate schedule (different rate for each $10,000 bracket) b) Process 50,000 wire transfer requests, each requiring OFAC sanctions screening via an external service call c) Generate month-end statements for 500,000 accounts d) Reconcile 10 million transactions against an external clearing house file e) Apply a regulatory rate change to all fixed-rate mortgage accounts f) Process returned check items, each requiring individual notification and fee assessment
Exercise 25: Complete Batch Program Design
Design (outline level, not full code) a batch program for the HA Banking system that processes daily ACH (Automated Clearing House) transactions. The program must:
- Read ACH items from an input queue (multi-row FETCH)
- Validate each item (account exists, sufficient funds, not blocked)
- Apply the transaction (MERGE into account balance)
- Write audit trail (multi-row INSERT, non-atomic)
- Update account summary with running daily total (OLAP function)
- Handle exceptions individually (cursor-based error processing)
- Track all balance changes temporally (temporal table)
For each step, specify: - Which technique(s) from this chapter - Why that technique fits - What SQLCODE handling is needed - What the recovery strategy is if the step fails
Exercise 26: Performance Estimation
A batch program currently processes 20 million rows using single-row FETCH, single-row UPDATE, and single-row INSERT (one audit record per transaction). Average thread switch time is 0.04 ms. Average SQL execution time per statement is 0.15 ms.
Calculate: a) Current total elapsed time for thread switches only b) Current total elapsed time for SQL execution only c) Projected thread-switch time with multi-row operations (rowset 500) d) Projected SQL execution time (assume multi-row SQL time = single-row time * 1.2 per batch due to array processing overhead) e) Total projected savings in hours
Exercise 27: Migration Risk Assessment
You're tasked with converting a 15-year-old cursor-loop program to use multi-row operations. The program has 4,200 lines of COBOL, processes 30 million rows nightly, and has not been modified in 3 years. List:
a) Five specific risks of the conversion b) Your testing strategy for each risk c) The rollback plan if the converted program fails in production d) How you would measure success (specific metrics)
Exercise 28: Temporal Table Storage Planning
Pinnacle Health's CLAIMS table has 50 million current rows, each averaging 800 bytes. The table averages 3 updates per row per year. They want to implement system-period temporal with a 10-year retention policy.
Calculate: a) History table row count after 1 year, 5 years, and 10 years (steady state) b) History table storage requirements at each interval (include 30% overhead for index and free space) c) Annual purge job I/O volume d) Impact on DASD capacity planning
Exercise 29: Recursive CTE Performance Testing
Write a test plan for validating a recursive CTE that traverses a 200,000-node hierarchy. Include:
a) Test data generation strategy (how to create a realistic hierarchy) b) Specific test cases for edge conditions (single-node hierarchy, maximum depth, wide trees, narrow trees) c) Performance benchmarks to validate (response time, TEMP space usage, CPU time) d) What EXPLAIN output you would check and what you'd look for
Exercise 30: OLAP Function vs. COBOL Logic Comparison
Write both versions of the following requirement and compare them:
Requirement: For each account, flag the first transaction that causes the running balance to exceed $100,000 (the "high-value threshold crossing").
Version 1: COBOL cursor loop with running balance accumulation and threshold check. Version 2: SQL with SUM() OVER for running balance, LAG() on the running balance to detect the crossing, and a CASE expression for the flag.
Compare: lines of code, number of SQL statements, thread switches for 1 million rows, maintainability, and testability.