30 min read

> "Sort is the unsung hero of batch processing. Every night, trillions of dollars move through sorted transaction files — and if the sort fails, the bank doesn't open in the morning." — Maria Chen, reflecting on her first production sort ABEND

Chapter 15: Sort and Merge

"Sort is the unsung hero of batch processing. Every night, trillions of dollars move through sorted transaction files — and if the sort fails, the bank doesn't open in the morning." — Maria Chen, reflecting on her first production sort ABEND

15.1 Introduction: Why Sorting Matters

In the world of mainframe batch processing, sorting is not merely a convenience — it is a foundational operation. Consider the nightly batch cycle at GlobalBank: 2.3 million transactions must be sorted by account number before the balance update program can process them sequentially against the account master file. Without that sort step, the entire nightly cycle grinds to a halt.

Sorting in COBOL serves several critical purposes:

  1. Sequential file matching: When two files must be processed together (matched by key), both files must be in the same key sequence.
  2. Report generation: Reports almost always require data in a specific order — by region, by date, by customer, by amount.
  3. Deduplication: Sorting brings duplicate records together, making them easy to detect and eliminate.
  4. Performance optimization: Many batch algorithms assume sorted input. A merge-based update against a master file, for instance, requires both files to be sorted on the same key.
  5. Regulatory compliance: Audit trails often require records in chronological order, and regulatory reports demand specific sort sequences.

COBOL provides two built-in verbs for ordering data: SORT and MERGE. These verbs invoke the system's sort utility (such as IBM's DFSORT or Syncsort's SyncSort on z/OS) through a high-level, declarative interface. You describe what you want sorted and how — the runtime handles the rest.

💡 Why Not Just Use JCL SORT? You can absolutely sort files outside of COBOL using JCL and a sort utility directly. In fact, for simple sorts with no data transformation, that is often the preferred approach. But when you need to filter, transform, or validate records during the sort — that is where COBOL's SORT verb with INPUT and OUTPUT PROCEDUREs shines. It gives you the power of a high-performance sort engine combined with the full expressiveness of COBOL's data manipulation capabilities.

In this chapter, we will explore the SORT and MERGE verbs in depth, from simple single-key sorts through complex multi-key operations with filtering and transformation. We will examine both the USING/GIVING shorthand and the more powerful INPUT/OUTPUT PROCEDURE approach. Along the way, we will see how GlobalBank sorts its daily transaction file and how MedClaim prepares claims for batch adjudication.


15.2 The SORT Verb: Fundamentals

The SORT verb arranges records in a specified order based on one or more key fields. At its simplest, a SORT reads records from an input file, sorts them, and writes them to an output file. Let us begin with the basic syntax.

15.2.1 Basic SORT Syntax

SORT sort-file-name
    ON {ASCENDING | DESCENDING} KEY data-name-1 [data-name-2 ...]
    [ON {ASCENDING | DESCENDING} KEY data-name-3 ...]
    [WITH DUPLICATES IN ORDER]
    [COLLATING SEQUENCE IS alphabet-name]
    {USING input-file-name [input-file-name-2 ...]}
    {GIVING output-file-name [output-file-name-2 ...]}

Several elements require explanation:

  • sort-file-name: This is not a regular file. It is a special work file declared with an SD (Sort Description) entry in the FILE SECTION. The system uses this as temporary workspace during the sort.
  • ASCENDING/DESCENDING KEY: Specifies the sort order and identifies which fields to sort on. You can specify multiple KEY clauses for multi-level sorting.
  • WITH DUPLICATES IN ORDER: Guarantees that records with equal key values retain their original input order (a stable sort). Without this clause, the relative order of equal-key records is implementation-defined.
  • COLLATING SEQUENCE: Overrides the default character comparison sequence (EBCDIC on mainframes, ASCII on open systems).
  • USING: Names the input file(s) — the sort reads from these automatically.
  • GIVING: Names the output file(s) — the sort writes results here automatically.

15.2.2 The Sort Description (SD) Entry

Before you can sort, you must declare a sort work file. This uses the SD (Sort Description) level indicator instead of the usual FD:

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SORT-WORK-FILE ASSIGN TO SORTWK01.
           SELECT INPUT-FILE     ASSIGN TO INFILE.
           SELECT OUTPUT-FILE    ASSIGN TO OUTFILE.

       DATA DIVISION.
       FILE SECTION.

       SD  SORT-WORK-FILE.
       01  SORT-RECORD.
           05  SR-ACCOUNT-NUM     PIC X(10).
           05  SR-TRANS-DATE      PIC X(08).
           05  SR-TRANS-TYPE      PIC X(01).
           05  SR-AMOUNT          PIC S9(09)V99.
           05  SR-DESCRIPTION     PIC X(30).
           05  FILLER             PIC X(40).

       FD  INPUT-FILE.
       01  INPUT-RECORD           PIC X(100).

       FD  OUTPUT-FILE.
       01  OUTPUT-RECORD          PIC X(100).

⚠️ Critical Rule: You never explicitly OPEN or CLOSE a sort work file (the SD file). The SORT verb manages its lifecycle automatically. Attempting to OPEN an SD file will cause a compilation error.

The SD entry does not specify BLOCK CONTAINS, RECORD CONTAINS, or LABEL RECORDS clauses (though RECORD CONTAINS is permitted on some compilers). The sort utility manages its own blocking and buffering.

15.2.3 JCL Considerations

On z/OS, the sort work file requires sort work datasets defined in JCL:

//SORTWK01 DD DSN=&&SORTWK1,SPACE=(CYL,(10,5)),UNIT=SYSDA
//SORTWK02 DD DSN=&&SORTWK2,SPACE=(CYL,(10,5)),UNIT=SYSDA
//SORTWK03 DD DSN=&&SORTWK3,SPACE=(CYL,(10,5)),UNIT=SYSDA

📊 Performance Note: The number and size of sort work datasets directly affect sort performance. A general rule of thumb is to allocate sort work space equal to at least twice the size of the input data. For very large sorts, more work datasets allow the sort utility to parallelize its I/O.


15.3 Simple Sorting with USING and GIVING

The USING and GIVING phrases provide the simplest form of sorting. The SORT verb automatically opens the input and output files, reads all records, sorts them, writes the sorted records, and closes the files.

15.3.1 Single-Key Ascending Sort

Here is a complete program that sorts a transaction file by account number:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SORT-SIMPLE.
      *================================================================
      * Program:  SORT-SIMPLE
      * Purpose:  Sort transaction file by account number (ascending)
      * Author:   Student Mainframe Lab - Chapter 15
      *================================================================

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SORT-FILE   ASSIGN TO SORTWK01.
           SELECT TRANS-IN    ASSIGN TO INFILE.
           SELECT TRANS-OUT   ASSIGN TO OUTFILE.

       DATA DIVISION.
       FILE SECTION.

       SD  SORT-FILE.
       01  SORT-REC.
           05  SR-ACCT-NUM    PIC X(10).
           05  SR-TRANS-DATE  PIC 9(08).
           05  SR-TRANS-TYPE  PIC X(01).
               88  SR-DEPOSIT     VALUE 'D'.
               88  SR-WITHDRAWAL  VALUE 'W'.
               88  SR-TRANSFER    VALUE 'T'.
           05  SR-AMOUNT      PIC S9(09)V99 COMP-3.
           05  SR-DESC        PIC X(30).
           05  FILLER         PIC X(34).

       FD  TRANS-IN
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TRANS-IN-REC       PIC X(90).

       FD  TRANS-OUT
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TRANS-OUT-REC      PIC X(90).

       PROCEDURE DIVISION.
       0000-MAIN.
           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               USING TRANS-IN
               GIVING TRANS-OUT
           STOP RUN.

That is the entire program. The SORT verb handles all file I/O. Notice:

  • No OPEN or CLOSE statements for any of the three files
  • No READ or WRITE statements
  • No PERFORM loops
  • The PROCEDURE DIVISION is just two statements: SORT and STOP RUN

Best Practice: For simple sorts that require no filtering or transformation, the USING/GIVING approach is preferred. It is concise, clear, and often generates more efficient sort utility calls because the sort engine can optimize I/O without COBOL program intervention.

15.3.2 Multi-Key Sorting

Real-world sorts almost always involve multiple keys. GlobalBank's nightly batch requires transactions sorted first by account number (to match against the master file), then by transaction date (for chronological processing within each account), and finally by amount descending (to process the largest transactions first for overdraft detection):

           SORT SORT-FILE
               ON ASCENDING  KEY SR-ACCT-NUM
               ON ASCENDING  KEY SR-TRANS-DATE
               ON DESCENDING KEY SR-AMOUNT
               USING TRANS-IN
               GIVING TRANS-OUT

You can also combine multiple keys in a single ON clause:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
                                SR-TRANS-DATE
               ON DESCENDING KEY SR-AMOUNT
               USING TRANS-IN
               GIVING TRANS-OUT

When multiple keys share the same sort direction, listing them together is more readable. The sort processes keys left to right in the order specified — the first key is the major sort key, and subsequent keys are minor sort keys used to break ties.

💡 How Multi-Key Sorting Works: Think of it as "sort by account number first; within the same account number, sort by date; within the same account and date, sort by amount descending." Each subsequent key only matters when all previous keys are equal.

15.3.3 Multiple Input Files

The USING phrase can name more than one input file. The sort reads and combines all records from all named files, then sorts them together:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               USING TRANS-FILE-1
                     TRANS-FILE-2
                     TRANS-FILE-3
               GIVING SORTED-TRANS

This is useful when transactions arrive from multiple sources (ATM transactions, online banking, branch transactions) and must be combined into a single sorted file.

15.3.4 Multiple Output Files

Similarly, GIVING can name multiple files. Each output file receives a complete copy of the sorted data:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               USING TRANS-IN
               GIVING SORTED-TRANS
                      SORTED-TRANS-BACKUP

⚠️ Note: Multiple GIVING files each get the entire sorted output — this is not a split operation. If you need to split sorted output into separate files based on criteria, use an OUTPUT PROCEDURE.


15.4 INPUT PROCEDURE: Filtering and Transforming Before Sort

The USING phrase reads every record from the input file and passes it directly to the sort. But what if you need to:

  • Filter records (sort only deposits, ignore withdrawals)?
  • Transform data (convert date formats before sorting)?
  • Validate records (reject malformed records)?
  • Read from multiple sources with different record layouts?

This is where the INPUT PROCEDURE comes in. An INPUT PROCEDURE is a section or range of paragraphs in your PROCEDURE DIVISION that the SORT verb invokes to supply records. Instead of reading from a file automatically, the sort calls your code, and your code feeds records to the sort one at a time using the RELEASE statement.

15.4.1 INPUT PROCEDURE Syntax

SORT sort-file-name
    ON ASCENDING KEY key-field
    INPUT PROCEDURE IS section-name-1
                    [THRU section-name-2]
    GIVING output-file-name

Or equivalently:

    INPUT PROCEDURE IS paragraph-name-1
                    [THRU paragraph-name-2]

15.4.2 The RELEASE Statement

Inside an INPUT PROCEDURE, you use RELEASE to pass a record to the sort:

RELEASE sort-record-name FROM data-name

Or simply:

RELEASE sort-record-name

The first form moves data from data-name to the sort record and then releases it. The second form assumes you have already moved data into the sort record area.

15.4.3 Example: Filtering During Sort

Here is a program that sorts only deposit transactions, skipping withdrawals and transfers:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SORT-FILTER.
      *================================================================
      * Program:  SORT-FILTER
      * Purpose:  Sort only deposit transactions by account + date
      * GlobalBank: Filter deposits for interest calculation run
      *================================================================

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SORT-FILE  ASSIGN TO SORTWK01.
           SELECT TRANS-IN   ASSIGN TO INFILE.
           SELECT TRANS-OUT  ASSIGN TO OUTFILE.

       DATA DIVISION.
       FILE SECTION.

       SD  SORT-FILE.
       01  SORT-REC.
           05  SR-ACCT-NUM    PIC X(10).
           05  SR-TRANS-DATE  PIC 9(08).
           05  SR-TRANS-TYPE  PIC X(01).
               88  SR-DEPOSIT     VALUE 'D'.
               88  SR-WITHDRAWAL  VALUE 'W'.
               88  SR-TRANSFER    VALUE 'T'.
           05  SR-AMOUNT      PIC S9(09)V99 COMP-3.
           05  SR-DESC        PIC X(30).
           05  FILLER         PIC X(34).

       FD  TRANS-IN
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TRANS-IN-REC.
           05  TI-ACCT-NUM    PIC X(10).
           05  TI-TRANS-DATE  PIC 9(08).
           05  TI-TRANS-TYPE  PIC X(01).
           05  TI-AMOUNT      PIC S9(09)V99 COMP-3.
           05  TI-DESC        PIC X(30).
           05  FILLER         PIC X(34).

       FD  TRANS-OUT
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TRANS-OUT-REC      PIC X(90).

       WORKING-STORAGE SECTION.
       01  WS-EOF-FLAG        PIC X(01) VALUE 'N'.
           88  END-OF-FILE    VALUE 'Y'.
       01  WS-RECORDS-READ    PIC 9(09) VALUE ZERO.
       01  WS-RECORDS-SORTED  PIC 9(09) VALUE ZERO.

       PROCEDURE DIVISION.
       0000-MAIN.
           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               ON ASCENDING KEY SR-TRANS-DATE
               INPUT PROCEDURE IS 1000-SELECT-DEPOSITS
               GIVING TRANS-OUT

           DISPLAY 'RECORDS READ:   ' WS-RECORDS-READ
           DISPLAY 'RECORDS SORTED: ' WS-RECORDS-SORTED
           STOP RUN.

       1000-SELECT-DEPOSITS.
           OPEN INPUT TRANS-IN
           PERFORM 1100-READ-AND-FILTER
               UNTIL END-OF-FILE
           CLOSE TRANS-IN.

       1100-READ-AND-FILTER.
           READ TRANS-IN
               AT END
                   SET END-OF-FILE TO TRUE
               NOT AT END
                   ADD 1 TO WS-RECORDS-READ
                   IF TI-TRANS-TYPE = 'D'
                       RELEASE SORT-REC FROM TRANS-IN-REC
                       ADD 1 TO WS-RECORDS-SORTED
                   END-IF
           END-READ.

📊 Counting Records: Notice how we maintain counters for records read and records released. This is a best practice for auditability — production batch jobs almost always report record counts for reconciliation.

15.4.4 Example: Transforming During Sort

Sometimes you need to modify records before sorting. At GlobalBank, branch codes were recently restructured from a 3-character to a 5-character format. Legacy transaction files still contain the old codes, but the sort must use the new format:

       1000-TRANSFORM-AND-SORT.
           OPEN INPUT TRANS-IN
           PERFORM 1100-PROCESS-RECORD
               UNTIL END-OF-FILE
           CLOSE TRANS-IN.

       1100-PROCESS-RECORD.
           READ TRANS-IN
               AT END
                   SET END-OF-FILE TO TRUE
               NOT AT END
                   ADD 1 TO WS-RECORDS-READ
                   MOVE TRANS-IN-REC TO SORT-REC
                   PERFORM 1200-CONVERT-BRANCH-CODE
                   RELEASE SORT-REC
                   ADD 1 TO WS-RECORDS-SORTED
           END-READ.

       1200-CONVERT-BRANCH-CODE.
           EVALUATE SR-OLD-BRANCH
               WHEN 'NYC'
                   MOVE 'NYC01' TO SR-NEW-BRANCH
               WHEN 'CHI'
                   MOVE 'CHI01' TO SR-NEW-BRANCH
               WHEN 'LAX'
                   MOVE 'LAX01' TO SR-NEW-BRANCH
               WHEN OTHER
                   MOVE 'UNK00' TO SR-NEW-BRANCH
                   ADD 1 TO WS-UNKNOWN-BRANCHES
           END-EVALUATE.

⚠️ Important Rule: Inside an INPUT PROCEDURE, you must not reference the input file named in a USING clause (because there is no USING clause — you replaced it). You manage the input file yourself: you OPEN it, READ from it, and CLOSE it. The only "special" operation is the RELEASE statement that feeds records to the sort.


15.5 OUTPUT PROCEDURE: Processing After Sort

Just as an INPUT PROCEDURE replaces USING, an OUTPUT PROCEDURE replaces GIVING. An OUTPUT PROCEDURE is a section or paragraph range that the SORT invokes after all records have been sorted. Your code retrieves sorted records one at a time using the RETURN statement.

15.5.1 OUTPUT PROCEDURE Syntax

SORT sort-file-name
    ON ASCENDING KEY key-field
    USING input-file-name
    OUTPUT PROCEDURE IS section-name-1
                     [THRU section-name-2]

15.5.2 The RETURN Statement

The RETURN statement retrieves the next record from the sort in sorted order:

RETURN sort-file-name [INTO data-name]
    AT END imperative-statement
    [NOT AT END imperative-statement]
END-RETURN

The AT END condition is raised when all sorted records have been retrieved. This parallels the READ statement's AT END for regular files.

15.5.3 Example: Control Break Report from Sorted Data

This is one of the most common patterns in batch COBOL: sort a file, then process the sorted output to produce a control break report. At GlobalBank, Derek needs to produce a daily summary showing total deposits and withdrawals per account:

       PROCEDURE DIVISION.
       0000-MAIN.
           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               ON ASCENDING KEY SR-TRANS-DATE
               USING TRANS-IN
               OUTPUT PROCEDURE IS 2000-PRODUCE-REPORT

           DISPLAY 'REPORT COMPLETE. ACCOUNTS: '
                   WS-ACCOUNT-COUNT
           STOP RUN.

       2000-PRODUCE-REPORT.
           OPEN OUTPUT REPORT-FILE
           PERFORM 2050-WRITE-REPORT-HEADERS
           PERFORM 2100-GET-FIRST-RECORD
           PERFORM 2200-PROCESS-ACCOUNT
               UNTIL END-OF-SORT
           PERFORM 2800-WRITE-GRAND-TOTALS
           CLOSE REPORT-FILE.

       2100-GET-FIRST-RECORD.
           RETURN SORT-FILE INTO WS-TRANS-REC
               AT END
                   SET END-OF-SORT TO TRUE
               NOT AT END
                   MOVE WT-ACCT-NUM TO WS-PREV-ACCT
           END-RETURN.

       2200-PROCESS-ACCOUNT.
           MOVE ZEROS TO WS-ACCT-DEPOSITS
                         WS-ACCT-WITHDRAWALS
                         WS-ACCT-TRANS-COUNT
           PERFORM 2300-ACCUMULATE-ACCOUNT
               UNTIL END-OF-SORT
               OR WT-ACCT-NUM NOT = WS-PREV-ACCT
           PERFORM 2400-WRITE-ACCOUNT-SUMMARY
           ADD 1 TO WS-ACCOUNT-COUNT
           IF NOT END-OF-SORT
               MOVE WT-ACCT-NUM TO WS-PREV-ACCT
           END-IF.

       2300-ACCUMULATE-ACCOUNT.
           ADD 1 TO WS-ACCT-TRANS-COUNT
           EVALUATE WT-TRANS-TYPE
               WHEN 'D'
                   ADD WT-AMOUNT TO WS-ACCT-DEPOSITS
               WHEN 'W'
                   ADD WT-AMOUNT TO WS-ACCT-WITHDRAWALS
           END-EVALUATE
           RETURN SORT-FILE INTO WS-TRANS-REC
               AT END
                   SET END-OF-SORT TO TRUE
           END-RETURN.

       2400-WRITE-ACCOUNT-SUMMARY.
           MOVE WS-PREV-ACCT       TO RPT-ACCT-NUM
           MOVE WS-ACCT-DEPOSITS   TO RPT-DEPOSITS
           MOVE WS-ACCT-WITHDRAWALS TO RPT-WITHDRAWALS
           COMPUTE RPT-NET = WS-ACCT-DEPOSITS
                            - WS-ACCT-WITHDRAWALS
           MOVE WS-ACCT-TRANS-COUNT TO RPT-TRANS-CNT
           WRITE REPORT-REC FROM WS-DETAIL-LINE
               AFTER ADVANCING 1 LINE.

🔗 Cross-Reference: This control-break pattern within an OUTPUT PROCEDURE connects directly to the Report Writer approach we explore in Chapter 16. Many of the same reports can be written either way — the choice depends on complexity and team preference.


15.6 Combining INPUT and OUTPUT PROCEDUREs

You can use both an INPUT PROCEDURE and an OUTPUT PROCEDURE in the same SORT statement:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               INPUT PROCEDURE IS 1000-FILTER-INPUT
               OUTPUT PROCEDURE IS 2000-PRODUCE-REPORT

This gives you maximum control: you filter and transform records on the way in, and you process sorted records on the way out. The sort utility handles everything in between.

15.6.1 Complete Example: GlobalBank Transaction Sort

Let us build a complete program that Maria Chen uses at GlobalBank. This program:

  1. Reads daily transactions from multiple sources
  2. Validates each transaction (INPUT PROCEDURE)
  3. Sorts by account number, then date, then amount descending
  4. Produces a sorted output file AND a summary report (OUTPUT PROCEDURE)
       IDENTIFICATION DIVISION.
       PROGRAM-ID. GBSORT01.
      *================================================================
      * Program:  GBSORT01
      * Purpose:  GlobalBank daily transaction sort with validation
      *           and summary reporting
      * Author:   Maria Chen / Derek Washington
      * Date:     2024-01-15
      *================================================================

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SORT-WORK     ASSIGN TO SORTWK01.
           SELECT TXN-INPUT     ASSIGN TO TXNIN.
           SELECT TXN-SORTED    ASSIGN TO TXNOUT.
           SELECT ERROR-FILE    ASSIGN TO ERRFILE.
           SELECT SUMMARY-RPT   ASSIGN TO SUMRPT.

       DATA DIVISION.
       FILE SECTION.

       SD  SORT-WORK.
       01  SORT-REC.
           05  SR-ACCT-NUM     PIC X(10).
           05  SR-TXN-DATE     PIC 9(08).
           05  SR-TXN-TIME     PIC 9(06).
           05  SR-TXN-TYPE     PIC X(01).
               88 SR-VALID-TYPE  VALUE 'D' 'W' 'T' 'F' 'I'.
           05  SR-AMOUNT       PIC S9(09)V99 COMP-3.
           05  SR-BRANCH       PIC X(05).
           05  SR-TELLER       PIC X(06).
           05  SR-DESC         PIC X(30).
           05  FILLER          PIC X(22).

       FD  TXN-INPUT
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TXN-IN-REC         PIC X(100).

       FD  TXN-SORTED
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  TXN-SORTED-REC     PIC X(100).

       FD  ERROR-FILE
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  ERROR-REC           PIC X(150).

       FD  SUMMARY-RPT
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  SUMMARY-REC         PIC X(132).

       WORKING-STORAGE SECTION.
       01  WS-FLAGS.
           05  WS-EOF-INPUT    PIC X(01) VALUE 'N'.
               88  EOF-INPUT   VALUE 'Y'.
           05  WS-EOF-SORT     PIC X(01) VALUE 'N'.
               88  EOF-SORT    VALUE 'Y'.

       01  WS-COUNTERS.
           05  WS-READ-CNT     PIC 9(09) VALUE ZERO.
           05  WS-VALID-CNT    PIC 9(09) VALUE ZERO.
           05  WS-ERROR-CNT    PIC 9(09) VALUE ZERO.
           05  WS-WRITTEN-CNT  PIC 9(09) VALUE ZERO.

       01  WS-CURRENT-REC.
           05  WC-ACCT-NUM     PIC X(10).
           05  WC-TXN-DATE     PIC 9(08).
           05  WC-TXN-TIME     PIC 9(06).
           05  WC-TXN-TYPE     PIC X(01).
           05  WC-AMOUNT       PIC S9(09)V99 COMP-3.
           05  WC-BRANCH       PIC X(05).
           05  WC-TELLER       PIC X(06).
           05  WC-DESC         PIC X(30).
           05  FILLER          PIC X(22).

       01  WS-ERROR-DETAIL.
           05  WE-RECORD       PIC X(100).
           05  WE-REASON       PIC X(50).

       01  WS-PREV-ACCT        PIC X(10).
       01  WS-ACCT-TOTAL       PIC S9(11)V99 VALUE ZERO.
       01  WS-GRAND-TOTAL      PIC S9(13)V99 VALUE ZERO.
       01  WS-ACCT-COUNT       PIC 9(07)     VALUE ZERO.

       PROCEDURE DIVISION.
       0000-MAIN.
           SORT SORT-WORK
               ON ASCENDING  KEY SR-ACCT-NUM
               ON ASCENDING  KEY SR-TXN-DATE
               ON ASCENDING  KEY SR-TXN-TIME
               ON DESCENDING KEY SR-AMOUNT
               WITH DUPLICATES IN ORDER
               INPUT PROCEDURE IS 1000-VALIDATE-INPUT
               OUTPUT PROCEDURE IS 2000-WRITE-OUTPUT

           DISPLAY '========================================='
           DISPLAY 'GBSORT01 PROCESSING COMPLETE'
           DISPLAY '  RECORDS READ:    ' WS-READ-CNT
           DISPLAY '  RECORDS SORTED:  ' WS-VALID-CNT
           DISPLAY '  RECORDS REJECTED:' WS-ERROR-CNT
           DISPLAY '  RECORDS WRITTEN: ' WS-WRITTEN-CNT
           DISPLAY '  ACCOUNTS FOUND:  ' WS-ACCT-COUNT
           DISPLAY '========================================='
           STOP RUN.

       1000-VALIDATE-INPUT.
           OPEN INPUT  TXN-INPUT
           OPEN OUTPUT ERROR-FILE
           PERFORM 1100-READ-VALIDATE
               UNTIL EOF-INPUT
           CLOSE TXN-INPUT
           CLOSE ERROR-FILE.

       1100-READ-VALIDATE.
           READ TXN-INPUT INTO WS-CURRENT-REC
               AT END
                   SET EOF-INPUT TO TRUE
               NOT AT END
                   ADD 1 TO WS-READ-CNT
                   PERFORM 1200-VALIDATE-RECORD
           END-READ.

       1200-VALIDATE-RECORD.
      *    Check account number is not spaces
           IF WC-ACCT-NUM = SPACES
               MOVE 'BLANK ACCOUNT NUMBER' TO WE-REASON
               PERFORM 1300-WRITE-ERROR
               RETURN
           END-IF

      *    Check transaction date is numeric and valid range
           IF WC-TXN-DATE NOT NUMERIC
               MOVE 'NON-NUMERIC DATE' TO WE-REASON
               PERFORM 1300-WRITE-ERROR
               RETURN
           END-IF

           IF WC-TXN-DATE < 20200101
           OR WC-TXN-DATE > 20251231
               MOVE 'DATE OUT OF RANGE' TO WE-REASON
               PERFORM 1300-WRITE-ERROR
               RETURN
           END-IF

      *    Check transaction type is valid
           IF WC-TXN-TYPE NOT = 'D' AND 'W' AND 'T'
              AND 'F' AND 'I'
               MOVE 'INVALID TRANSACTION TYPE' TO WE-REASON
               PERFORM 1300-WRITE-ERROR
               RETURN
           END-IF

      *    Check amount is reasonable
           IF WC-AMOUNT = ZERO
               MOVE 'ZERO AMOUNT' TO WE-REASON
               PERFORM 1300-WRITE-ERROR
               RETURN
           END-IF

      *    Record passed all validations — release to sort
           RELEASE SORT-REC FROM WS-CURRENT-REC
           ADD 1 TO WS-VALID-CNT.

       1300-WRITE-ERROR.
           MOVE WS-CURRENT-REC TO WE-RECORD
           WRITE ERROR-REC FROM WS-ERROR-DETAIL
           ADD 1 TO WS-ERROR-CNT.

       2000-WRITE-OUTPUT.
           OPEN OUTPUT TXN-SORTED
           OPEN OUTPUT SUMMARY-RPT
           PERFORM 2100-GET-FIRST-SORTED
           PERFORM 2200-PROCESS-SORTED
               UNTIL EOF-SORT
           PERFORM 2500-WRITE-GRAND-TOTALS
           CLOSE TXN-SORTED
           CLOSE SUMMARY-RPT.

       2100-GET-FIRST-SORTED.
           RETURN SORT-WORK INTO WS-CURRENT-REC
               AT END
                   SET EOF-SORT TO TRUE
               NOT AT END
                   MOVE WC-ACCT-NUM TO WS-PREV-ACCT
                   MOVE ZERO TO WS-ACCT-TOTAL
           END-RETURN.

       2200-PROCESS-SORTED.
      *    Write the sorted record
           WRITE TXN-SORTED-REC FROM WS-CURRENT-REC
           ADD 1 TO WS-WRITTEN-CNT
           ADD WC-AMOUNT TO WS-ACCT-TOTAL

      *    Get next record
           RETURN SORT-WORK INTO WS-CURRENT-REC
               AT END
                   SET EOF-SORT TO TRUE
                   PERFORM 2300-ACCOUNT-BREAK
               NOT AT END
                   IF WC-ACCT-NUM NOT = WS-PREV-ACCT
                       PERFORM 2300-ACCOUNT-BREAK
                       MOVE WC-ACCT-NUM TO WS-PREV-ACCT
                       MOVE ZERO TO WS-ACCT-TOTAL
                   END-IF
           END-RETURN.

       2300-ACCOUNT-BREAK.
           ADD 1 TO WS-ACCT-COUNT
           ADD WS-ACCT-TOTAL TO WS-GRAND-TOTAL
           PERFORM 2400-WRITE-ACCT-SUMMARY.

       2400-WRITE-ACCT-SUMMARY.
           INITIALIZE SUMMARY-REC
           STRING 'ACCOUNT: ' WS-PREV-ACCT
                  '  NET ACTIVITY: '
                  DELIMITED BY SIZE
                  INTO SUMMARY-REC
           WRITE SUMMARY-REC.

       2500-WRITE-GRAND-TOTALS.
           INITIALIZE SUMMARY-REC
           MOVE '========================================='
               TO SUMMARY-REC
           WRITE SUMMARY-REC
           INITIALIZE SUMMARY-REC
           STRING 'GRAND TOTAL NET ACTIVITY: '
                  DELIMITED BY SIZE
                  INTO SUMMARY-REC
           WRITE SUMMARY-REC.

🧪 Try It Yourself: In your Student Mainframe Lab, create a transaction file with some deliberately invalid records (blank account numbers, non-numeric dates, zero amounts). Run GBSORT01 and verify that the error file catches the bad records while the sorted output contains only valid records. Check that the record counts in the DISPLAY output reconcile: READ-CNT should equal VALID-CNT plus ERROR-CNT.


15.7 The MERGE Verb

While SORT arranges records from one or more unsorted files, MERGE combines records from two or more files that are already sorted on the same key. The MERGE verb interleaves records from its input files to produce a single, properly ordered output.

15.7.1 MERGE Syntax

MERGE merge-file-name
    ON {ASCENDING | DESCENDING} KEY data-name-1 [data-name-2 ...]
    [COLLATING SEQUENCE IS alphabet-name]
    USING input-file-1 input-file-2 [input-file-3 ...]
    {GIVING output-file-name |
     OUTPUT PROCEDURE IS section-name}

Key differences from SORT:

  • MERGE requires at least two input files in the USING clause (SORT requires at least one)
  • MERGE does not support INPUT PROCEDURE — the input files must already be in the correct sort order
  • MERGE does support OUTPUT PROCEDURE for post-merge processing
  • The merge file uses an SD entry, just like a sort file

⚠️ Critical Prerequisite: Each input file to MERGE must already be sorted on the same key(s) specified in the MERGE statement. If a file is not properly sorted, the results are undefined — the merge utility may produce incorrect output without any error message. Always verify your input file sort order!

15.7.2 MERGE Example: Combining Regional Transaction Files

At GlobalBank, each region produces its own sorted transaction file overnight. These must be merged into a single national file for consolidated processing:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. GBMERGE01.
      *================================================================
      * Program:  GBMERGE01
      * Purpose:  Merge regional transaction files into national file
      * Note:     All input files MUST be pre-sorted on account number
      *================================================================

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MERGE-FILE    ASSIGN TO SORTWK01.
           SELECT EAST-TRANS    ASSIGN TO EASTIN.
           SELECT CENTRAL-TRANS ASSIGN TO CENTRIN.
           SELECT WEST-TRANS    ASSIGN TO WESTIN.
           SELECT NATIONAL-FILE ASSIGN TO NATOUT.

       DATA DIVISION.
       FILE SECTION.

       SD  MERGE-FILE.
       01  MERGE-REC.
           05  MR-ACCT-NUM     PIC X(10).
           05  MR-TXN-DATE     PIC 9(08).
           05  MR-REGION       PIC X(02).
           05  FILLER          PIC X(80).

       FD  EAST-TRANS
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  EAST-REC            PIC X(100).

       FD  CENTRAL-TRANS
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  CENTRAL-REC         PIC X(100).

       FD  WEST-TRANS
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  WEST-REC            PIC X(100).

       FD  NATIONAL-FILE
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  NATIONAL-REC        PIC X(100).

       PROCEDURE DIVISION.
       0000-MAIN.
           MERGE MERGE-FILE
               ON ASCENDING KEY MR-ACCT-NUM
               ON ASCENDING KEY MR-TXN-DATE
               USING EAST-TRANS
                     CENTRAL-TRANS
                     WEST-TRANS
               GIVING NATIONAL-FILE
           STOP RUN.

15.7.3 MERGE with OUTPUT PROCEDURE

You can use an OUTPUT PROCEDURE with MERGE to process records as they come out of the merge. This is useful for detecting duplicates across the merged files:

           MERGE MERGE-FILE
               ON ASCENDING KEY MR-ACCT-NUM
               ON ASCENDING KEY MR-TXN-DATE
               USING EAST-TRANS
                     CENTRAL-TRANS
                     WEST-TRANS
               OUTPUT PROCEDURE IS 3000-DEDUP-AND-WRITE

       3000-DEDUP-AND-WRITE.
           OPEN OUTPUT NATIONAL-FILE
           MOVE LOW-VALUES TO WS-PREV-KEY
           PERFORM 3100-GET-MERGED
               UNTIL EOF-MERGE
           CLOSE NATIONAL-FILE.

       3100-GET-MERGED.
           RETURN MERGE-FILE INTO WS-MERGE-REC
               AT END
                   SET EOF-MERGE TO TRUE
               NOT AT END
                   IF WM-FULL-KEY = WS-PREV-KEY
                       ADD 1 TO WS-DUP-COUNT
                   ELSE
                       WRITE NATIONAL-REC FROM WS-MERGE-REC
                       ADD 1 TO WS-WRITE-COUNT
                       MOVE WM-FULL-KEY TO WS-PREV-KEY
                   END-IF
           END-RETURN.

💡 SORT vs. MERGE Performance: MERGE is significantly faster than SORT when input files are already sorted, because it avoids the internal sort phase entirely. A merge operation is O(n) — it simply interleaves already-ordered sequences. A sort is O(n log n). For very large files, this difference matters enormously.


15.8 Sort Error Handling — Defensive Programming

The theme of this chapter is Defensive Programming, and nowhere is it more important than in sort operations. A failed sort can halt an entire batch cycle. Let us examine how to detect and handle sort errors.

15.8.1 The SORT Special Register

After a SORT or MERGE statement executes, the special register SORT-RETURN contains the return code:

Value Meaning
0 Sort completed successfully
16 Sort terminated abnormally

You can check SORT-RETURN immediately after the SORT statement:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               USING TRANS-IN
               GIVING TRANS-OUT

           IF SORT-RETURN NOT = ZERO
               DISPLAY 'SORT FAILED WITH RC = ' SORT-RETURN
               MOVE 16 TO RETURN-CODE
               STOP RUN
           END-IF

15.8.2 Setting SORT-RETURN to Cancel a Sort

You can set SORT-RETURN to 16 inside an INPUT or OUTPUT PROCEDURE to force the sort to terminate:

       1200-VALIDATE-RECORD.
           IF WC-ERROR-COUNT > WS-MAX-ERRORS
               DISPLAY 'TOO MANY ERRORS - ABORTING SORT'
               MOVE 16 TO SORT-RETURN
           END-IF.

When you set SORT-RETURN to 16, the sort utility terminates as soon as control returns from your INPUT or OUTPUT PROCEDURE. This is the graceful way to abort a sort when data quality is too poor to continue.

⚠️ Defense Pattern — The Error Threshold: James Okafor at MedClaim follows a strict rule: "If more than 1% of records fail validation, something is wrong with the feed — don't sort, don't process, escalate immediately." This error threshold pattern is a defensive programming best practice:

       1200-CHECK-ERROR-THRESHOLD.
           IF WS-READ-CNT > 100
               COMPUTE WS-ERROR-PCT =
                   (WS-ERROR-CNT / WS-READ-CNT) * 100
               IF WS-ERROR-PCT > 1
                   DISPLAY 'ERROR RATE EXCEEDS 1% - ABORTING'
                   DISPLAY 'ERRORS: ' WS-ERROR-CNT
                       ' OF ' WS-READ-CNT ' RECORDS'
                   MOVE 16 TO SORT-RETURN
               END-IF
           END-IF.

15.8.3 Sort Return Code in JCL

The SORT-RETURN value flows through to the JCL COND parameter, allowing subsequent job steps to check whether the sort succeeded:

//STEP020  EXEC PGM=BALPROG
//         COND=(4,LT,STEP010)

This step runs only if STEP010 (the sort step) returned a code less than 4 — effectively, only if the sort succeeded.

15.8.4 Common Sort Failures and Their Causes

Failure Typical Cause Prevention
S0C7 ABEND Non-numeric data in numeric sort key Validate data in INPUT PROCEDURE
S001 ABEND I/O error on input/output file Check file status, verify DD statements
S37 ABEND Sort work space full Allocate sufficient SORTWK space
SB37 ABEND Output file space exhausted Estimate output size, use SMS-managed space
Incorrect results Input to MERGE not pre-sorted Verify sort order before MERGE
Incorrect results Wrong key field offset Verify SD record layout matches actual data

🔴 War Story: Derek Washington spent an entire afternoon debugging a sort that produced subtly wrong results. The sorted output looked sorted, but downstream account balancing was off. The culprit? The SD record layout had a FILLER field that was one byte too short, causing all subsequent fields to be misaligned. The sort was ordering by what it thought was the account number — but was actually the last digit of the account number followed by the first seven digits of the date. Lesson: always verify your SD layout against the actual file record layout, byte by byte.


15.9 External Sort Utilities: DFSORT and SyncSort

On IBM mainframes, the COBOL SORT verb invokes an external sort utility — most commonly DFSORT (IBM's licensed sort product) or SyncSort (now Precisely SyncSort). Understanding these utilities helps you optimize sort performance and leverage features beyond what COBOL's SORT verb directly exposes.

15.9.1 How COBOL Invokes the Sort Utility

When your COBOL program executes a SORT statement, the following happens:

  1. The COBOL runtime calls the sort utility's API
  2. If using USING/GIVING, the sort utility handles all I/O directly
  3. If using INPUT/OUTPUT PROCEDURE, the sort utility calls back to your COBOL code via the RELEASE/RETURN mechanism
  4. The sort utility manages sort work files, memory allocation, and the sort algorithm

You generally do not need to configure the sort utility — it works through sensible defaults. However, you can influence its behavior through:

  • JCL PARM statements on the sort step
  • SORTCNTL DD statements with sort control cards
  • DFSPARM DD for DFSORT-specific options

15.9.2 DFSORT Control Statements

Sometimes you want to use DFSORT features that COBOL's SORT verb cannot express. You can pass control statements through the DFSPARM DD:

//DFSPARM DD *
  OPTION DYNALLOC=(SYSDA,5)
  OPTION FILSZ=E1000000
/*
  • DYNALLOC: Dynamically allocates sort work datasets (saves you from coding SORTWK DD statements)
  • FILSZ: Tells the sort how many records to expect, enabling better memory allocation

15.9.3 When to Use JCL Sort Instead of COBOL SORT

For simple sort-only operations with no filtering or transformation, a standalone DFSORT step in JCL is often preferable:

//SORTJOB  EXEC PGM=SORT
//SYSOUT   DD SYSOUT=*
//SORTIN   DD DSN=PROD.TRANS.DAILY,DISP=SHR
//SORTOUT  DD DSN=PROD.TRANS.SORTED,DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(50,10)),UNIT=SYSDA
//SYSIN    DD *
  SORT FIELDS=(1,10,CH,A,11,8,CH,A)
  OPTION EQUALS
/*

Advantages of JCL sort: - No COBOL program to compile and maintain - Directly leverages all sort utility features - Often faster for simple sorts (no COBOL overhead) - Easier to modify (change JCL, not source code)

Advantages of COBOL SORT: - Complex filtering and transformation via INPUT/OUTPUT PROCEDUREs - Sort logic documented alongside business logic - Conditional sorting based on program state - Integration with program variables and control flow

📊 Decision Guide: Maria Chen's rule at GlobalBank: "If you're just reordering records, use JCL SORT. If you need to think about the records while sorting them, use COBOL SORT."

15.9.4 Performance Tuning the Sort

For large-volume sorts (millions of records), performance matters. Key tuning parameters:

  1. Memory allocation: More memory means fewer merge passes. DFSORT's MAINSIZE option controls this: OPTION MAINSIZE=MAX

  2. Sort work datasets: More datasets allow parallel I/O. Three to six sort work datasets is typical for large sorts.

  3. Key position: Keep sort keys at the beginning of the record when possible — some sort utilities can optimize key comparison when keys are contiguous and at the start of the record.

  4. Record length: Shorter records sort faster. If you are sorting very long records on a short key, consider extracting keys, sorting the key file, and then resequencing the original file (a tag sort).

  5. FASTSRT compiler option: The Enterprise COBOL FASTSRT compiler option allows the sort utility to handle I/O directly when USING/GIVING is specified, bypassing COBOL's I/O routines. This can significantly improve performance: jcl //COBOL.SYSIN DD * CBL FASTSRT

Best Practice: Always specify FASTSRT unless you have specific reasons not to. The only case where FASTSRT is incompatible is when the FD for the sort input/output file has special characteristics (APPLY WRITE-ONLY, certain LINAGE specifications, etc.).


15.10 Advanced Sort Patterns

15.10.1 Sorting with Variable-Length Records

COBOL's SORT handles variable-length records, but you must declare the record properly:

       SD  SORT-FILE
           RECORD IS VARYING IN SIZE
               FROM 50 TO 500 CHARACTERS
               DEPENDING ON WS-SORT-REC-LEN.
       01  SORT-REC.
           05  SR-REC-TYPE     PIC X(02).
           05  SR-KEY           PIC X(20).
           05  SR-DATA          PIC X(478).

15.10.2 Sorting a Table (Array) in Memory

The SORT verb works only with files, not with tables in memory. To sort an in-memory table, you have several options:

  1. Write the table to a temporary file, sort it, read it back — cumbersome but uses the efficient external sort
  2. Write your own sort algorithm — bubble sort for small tables, quicksort for larger ones
  3. Use SORT with an INPUT/OUTPUT PROCEDURE that RELEASEs table entries and RETURNs them back into the table

The third approach is elegant:

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 0100-LOAD-TABLE
           SORT SORT-FILE
               ON ASCENDING KEY SR-EMPLOYEE-ID
               INPUT PROCEDURE IS 1000-RELEASE-TABLE
               OUTPUT PROCEDURE IS 2000-RETURN-TABLE
           PERFORM 0200-PROCESS-SORTED-TABLE
           STOP RUN.

       1000-RELEASE-TABLE.
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-TABLE-SIZE
               RELEASE SORT-REC FROM TABLE-ENTRY(WS-IDX)
           END-PERFORM.

       2000-RETURN-TABLE.
           MOVE ZERO TO WS-IDX
           PERFORM 2100-GET-SORTED-ENTRY
               UNTIL EOF-SORT.

       2100-GET-SORTED-ENTRY.
           ADD 1 TO WS-IDX
           RETURN SORT-FILE INTO TABLE-ENTRY(WS-IDX)
               AT END
                   SET EOF-SORT TO TRUE
           END-RETURN.

🧪 Try It Yourself: Create a program that loads 20 employee records into a table, sorts the table by last name using the INPUT/OUTPUT PROCEDURE technique, and then displays the sorted table. Compare this approach with writing a simple bubble sort paragraph. Which is more readable? Which would you prefer in production code?

15.10.3 The WITH DUPLICATES IN ORDER Clause

By default, when two records have identical sort keys, their relative order in the output is implementation-dependent. The WITH DUPLICATES IN ORDER clause guarantees stability — records with equal keys appear in the same order as they appeared in the input.

This matters for audit trails and regulatory compliance:

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               WITH DUPLICATES IN ORDER
               USING TRANS-IN
               GIVING TRANS-OUT

When multiple transactions for the same account arrive in chronological order but are sorted only by account (not by time), WITHOUT the DUPLICATES IN ORDER clause, transactions within the same account might appear in random order. WITH the clause, they remain in their original chronological sequence.

📊 Performance Note: WITH DUPLICATES IN ORDER may slightly reduce sort performance because the sort utility must track original record positions. For very large files, test whether the performance impact is acceptable.


15.11 MedClaim Case Study: Sorting Claims for Batch Adjudication

Let us examine how MedClaim uses SORT in their claims processing pipeline. James Okafor's team needs to sort incoming claims for batch adjudication. The sort must:

  1. Filter out duplicate claim submissions (same claim ID submitted twice)
  2. Validate provider numbers against a valid range
  3. Sort by provider number, then claim date, for efficient provider-batch processing
  4. Track statistics for the nightly operations dashboard
       IDENTIFICATION DIVISION.
       PROGRAM-ID. MCSORT01.
      *================================================================
      * Program:  MCSORT01
      * Purpose:  MedClaim - Sort claims for batch adjudication
      * Author:   James Okafor / Sarah Kim
      * Notes:    Claims sorted by provider then date for efficient
      *           batch adjudication processing
      *================================================================

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT SORT-WORK     ASSIGN TO SORTWK01.
           SELECT CLAIMS-IN     ASSIGN TO CLMIN.
           SELECT CLAIMS-SORTED ASSIGN TO CLMOUT.
           SELECT DUPLICATES-FL ASSIGN TO DUPFILE.

       DATA DIVISION.
       FILE SECTION.

       SD  SORT-WORK.
       01  SORT-CLAIM.
           05  SC-PROVIDER-NUM  PIC X(10).
           05  SC-CLAIM-DATE    PIC 9(08).
           05  SC-CLAIM-ID      PIC X(15).
           05  SC-MEMBER-ID     PIC X(12).
           05  SC-DIAG-CODE     PIC X(07).
           05  SC-PROC-CODE     PIC X(05).
           05  SC-CHARGE-AMT    PIC S9(07)V99 COMP-3.
           05  SC-FACILITY      PIC X(06).
           05  FILLER           PIC X(30).

       FD  CLAIMS-IN
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  CLAIM-IN-REC         PIC X(100).

       FD  CLAIMS-SORTED
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  CLAIM-SORTED-REC     PIC X(100).

       FD  DUPLICATES-FL
           RECORDING MODE IS F
           BLOCK CONTAINS 0 RECORDS.
       01  DUPLICATE-REC        PIC X(100).

       WORKING-STORAGE SECTION.
       01  WS-FLAGS.
           05  WS-EOF-IN       PIC X VALUE 'N'.
               88  EOF-IN      VALUE 'Y'.
           05  WS-EOF-SORT     PIC X VALUE 'N'.
               88  EOF-SORT    VALUE 'Y'.

       01  WS-COUNTERS.
           05  WS-READ-CNT     PIC 9(07) VALUE ZERO.
           05  WS-RELEASED-CNT PIC 9(07) VALUE ZERO.
           05  WS-DUP-CNT      PIC 9(07) VALUE ZERO.
           05  WS-BAD-PROV-CNT PIC 9(07) VALUE ZERO.
           05  WS-WRITTEN-CNT  PIC 9(07) VALUE ZERO.

       01  WS-PREV-CLAIM-ID    PIC X(15) VALUE LOW-VALUES.

       01  WS-CLAIM-WORK.
           05  WW-PROVIDER-NUM  PIC X(10).
           05  WW-CLAIM-DATE    PIC 9(08).
           05  WW-CLAIM-ID      PIC X(15).
           05  WW-MEMBER-ID     PIC X(12).
           05  WW-DIAG-CODE     PIC X(07).
           05  WW-PROC-CODE     PIC X(05).
           05  WW-CHARGE-AMT    PIC S9(07)V99 COMP-3.
           05  WW-FACILITY      PIC X(06).
           05  FILLER           PIC X(30).

       PROCEDURE DIVISION.
       0000-MAIN.
           SORT SORT-WORK
               ON ASCENDING KEY SC-PROVIDER-NUM
               ON ASCENDING KEY SC-CLAIM-DATE
               WITH DUPLICATES IN ORDER
               INPUT PROCEDURE IS 1000-VALIDATE-CLAIMS
               OUTPUT PROCEDURE IS 2000-DEDUP-OUTPUT

           IF SORT-RETURN NOT = ZERO
               DISPLAY 'MCSORT01: SORT FAILED RC='
                       SORT-RETURN
               MOVE 16 TO RETURN-CODE
           ELSE
               DISPLAY 'MCSORT01: COMPLETE'
               DISPLAY '  READ:       ' WS-READ-CNT
               DISPLAY '  RELEASED:   ' WS-RELEASED-CNT
               DISPLAY '  BAD PROVDR: ' WS-BAD-PROV-CNT
               DISPLAY '  DUPLICATES: ' WS-DUP-CNT
               DISPLAY '  WRITTEN:    ' WS-WRITTEN-CNT
           END-IF
           STOP RUN.

       1000-VALIDATE-CLAIMS.
           OPEN INPUT CLAIMS-IN
           PERFORM 1100-READ-AND-CHECK
               UNTIL EOF-IN
           CLOSE CLAIMS-IN.

       1100-READ-AND-CHECK.
           READ CLAIMS-IN INTO WS-CLAIM-WORK
               AT END
                   SET EOF-IN TO TRUE
               NOT AT END
                   ADD 1 TO WS-READ-CNT
                   PERFORM 1200-VALIDATE-PROVIDER
           END-READ.

       1200-VALIDATE-PROVIDER.
      *    Provider numbers must start with 'PRV' and be
      *    followed by 7 numeric digits
           IF WW-PROVIDER-NUM(1:3) NOT = 'PRV'
               ADD 1 TO WS-BAD-PROV-CNT
           ELSE
               IF WW-PROVIDER-NUM(4:7) NOT NUMERIC
                   ADD 1 TO WS-BAD-PROV-CNT
               ELSE
                   RELEASE SORT-CLAIM FROM WS-CLAIM-WORK
                   ADD 1 TO WS-RELEASED-CNT
               END-IF
           END-IF.

       2000-DEDUP-OUTPUT.
           OPEN OUTPUT CLAIMS-SORTED
           OPEN OUTPUT DUPLICATES-FL
           MOVE LOW-VALUES TO WS-PREV-CLAIM-ID
           PERFORM 2100-GET-AND-DEDUP
               UNTIL EOF-SORT
           CLOSE CLAIMS-SORTED
           CLOSE DUPLICATES-FL.

       2100-GET-AND-DEDUP.
           RETURN SORT-WORK INTO WS-CLAIM-WORK
               AT END
                   SET EOF-SORT TO TRUE
               NOT AT END
                   IF WW-CLAIM-ID = WS-PREV-CLAIM-ID
                       WRITE DUPLICATE-REC
                           FROM WS-CLAIM-WORK
                       ADD 1 TO WS-DUP-CNT
                   ELSE
                       WRITE CLAIM-SORTED-REC
                           FROM WS-CLAIM-WORK
                       ADD 1 TO WS-WRITTEN-CNT
                       MOVE WW-CLAIM-ID
                           TO WS-PREV-CLAIM-ID
                   END-IF
           END-RETURN.

🔵 Sarah Kim's Perspective: "Notice that the deduplication in the OUTPUT PROCEDURE checks claim ID, not the sort keys (provider + date). The sort brings records into provider/date order for downstream processing, but the duplicate check uses the claim's unique identifier. This is a common pattern — sort on one key, deduplicate on another."


15.12 Sort Performance Considerations

Production sort jobs at GlobalBank process millions of records nightly. Performance tuning is not academic — it directly affects whether the batch window completes before the online system must come back up at 6:00 AM.

15.12.1 Factors Affecting Sort Performance

Factor Impact Optimization
Number of records Linear — doubling records roughly doubles sort time Pre-filter in INPUT PROCEDURE
Record length Longer records = more I/O Use tag sort for very long records
Number of keys Minimal impact on comparison time Group keys contiguously
Key position Keys at start of record can be optimized Place keys at beginning
Available memory More memory = fewer merge passes Use MAINSIZE=MAX
Sort work space More space = better parallelism Allocate generously
FASTSRT option Bypasses COBOL I/O routines Always use with USING/GIVING
Disk speed Direct impact on I/O-bound sorts Use fastest available DASD

15.12.2 The Tag Sort Technique

When records are very long (say, 2000 bytes) but the sort key is short (say, 20 bytes), you can dramatically improve performance with a tag sort:

  1. Extract the key plus a record sequence number into a short record
  2. Sort the short records
  3. Use the sequence numbers to resequence the original file

This reduces I/O during the sort phase by a factor of 100 in this example (20 bytes vs. 2000 bytes per record).

15.12.3 FASTSRT and Its Limitations

The FASTSRT compiler option tells Enterprise COBOL to let the sort utility handle I/O directly for USING and GIVING files. This bypasses COBOL's I/O routines, avoiding overhead from:

  • COBOL's internal buffering
  • AT END processing
  • Record area MOVEs

FASTSRT is incompatible with: - INPUT PROCEDURE or OUTPUT PROCEDURE (naturally — COBOL must handle I/O in these) - APPLY WRITE-ONLY on the FD - Certain LINAGE specifications - EXTERNAL files

When FASTSRT cannot be used, the compiler silently falls back to standard I/O without warning.


15.13 Sort and Merge Restrictions

Understanding the restrictions on SORT and MERGE prevents frustrating debugging sessions:

  1. No nested SORT/MERGE: You cannot execute a SORT inside an INPUT or OUTPUT PROCEDURE of another SORT. This restriction exists because both sorts would need the same sort utility resources.

  2. No GO TO out of procedures: You must not transfer control out of an INPUT or OUTPUT PROCEDURE using GO TO (some compilers allow it, but it causes undefined behavior).

  3. File restrictions: Inside an INPUT PROCEDURE, you cannot reference any file named in a USING clause of the same SORT. Inside an OUTPUT PROCEDURE, you cannot reference any file named in a GIVING clause.

  4. RELEASE only in INPUT PROCEDURE: The RELEASE statement is valid only within the INPUT PROCEDURE of the active SORT.

  5. RETURN only in OUTPUT PROCEDURE: The RETURN statement is valid only within the OUTPUT PROCEDURE of the active SORT or MERGE.

  6. SD files cannot be OPENed: Never issue OPEN or CLOSE for a sort/merge work file.

  7. MERGE input must be pre-sorted: Unlike SORT, MERGE assumes input files are already in the specified key order.

⚠️ Compiler Behavior Varies: Some COBOL compilers enforce these restrictions at compile time, others at runtime, and some not at all. Code that violates these rules may appear to work on one platform and fail mysteriously on another. Follow the rules regardless of what your specific compiler allows.


15.14 GnuCOBOL Sort Considerations

For students using GnuCOBOL in the Student Mainframe Lab, the SORT verb works but with some differences from Enterprise COBOL:

  1. No external sort utility: GnuCOBOL uses its own internal sort algorithm. There are no SORTWK DD statements — the SELECT for the sort file typically uses a temporary file path.

  2. ASSIGN clause: Instead of SORTWK01, use a file system path: cobol SELECT SORT-FILE ASSIGN TO "/tmp/sortwork". Or on Windows: cobol SELECT SORT-FILE ASSIGN TO "C:\TEMP\SORTWORK".

  3. No FASTSRT: This compiler option is Enterprise COBOL-specific.

  4. Performance: For most student-scale datasets (thousands of records), GnuCOBOL's sort performance is more than adequate. For production-scale datasets (millions of records), mainframe sort utilities are dramatically faster.

  5. SORT-RETURN: GnuCOBOL supports the SORT-RETURN special register, so your error-handling code is portable.

🧪 Try It Yourself: Compile and run the SORT-SIMPLE program from Section 15.3.1 using GnuCOBOL. Create a test input file with at least 50 records and verify the output is correctly sorted. Then modify it to use an INPUT PROCEDURE that filters out records with negative amounts.


15.15 Common Sort Patterns in Practice

15.15.1 Pattern: Sort-Match-Update

The most common use of SORT in batch processing is the sort-match-update pattern:

  1. Sort the transaction file on the same key as the master file
  2. Match transaction records against master records sequentially
  3. Update the master file with matching transactions
[Unsorted Transactions] → SORT → [Sorted Transactions]
                                         ↓
                              MATCH with [Master File]
                                         ↓
                              [Updated Master File]

This pattern is fundamental to batch processing and appears in nearly every mainframe application.

15.15.2 Pattern: Sort-Dedup

Sort records to bring duplicates together, then eliminate duplicates in the OUTPUT PROCEDURE:

           SORT SORT-FILE
               ON ASCENDING KEY SR-FULL-KEY
               USING RAW-INPUT
               OUTPUT PROCEDURE IS 5000-DEDUP

15.15.3 Pattern: Sort-Split

Sort a file and then split it into multiple output files based on record content:

       2000-SPLIT-OUTPUT.
           OPEN OUTPUT DEPOSIT-FILE
           OPEN OUTPUT WITHDRAWAL-FILE
           OPEN OUTPUT TRANSFER-FILE
           PERFORM 2100-GET-AND-SPLIT
               UNTIL EOF-SORT
           CLOSE DEPOSIT-FILE
                 WITHDRAWAL-FILE
                 TRANSFER-FILE.

       2100-GET-AND-SPLIT.
           RETURN SORT-FILE INTO WS-WORK-REC
               AT END
                   SET EOF-SORT TO TRUE
               NOT AT END
                   EVALUATE WW-TXN-TYPE
                       WHEN 'D'
                           WRITE DEPOSIT-REC
                               FROM WS-WORK-REC
                       WHEN 'W'
                           WRITE WITHDRAWAL-REC
                               FROM WS-WORK-REC
                       WHEN 'T'
                           WRITE TRANSFER-REC
                               FROM WS-WORK-REC
                   END-EVALUATE
           END-RETURN.

15.15.4 Pattern: Sort-Aggregate

Sort records and produce summary records (aggregating by key):

       3000-AGGREGATE-OUTPUT.
           OPEN OUTPUT SUMMARY-FILE
           PERFORM 3050-INIT-FIRST
           PERFORM 3100-AGGREGATE-LOOP
               UNTIL EOF-SORT
           PERFORM 3200-WRITE-FINAL-SUMMARY
           CLOSE SUMMARY-FILE.

15.16 Putting It All Together: A Production Sort Program Checklist

Based on Maria Chen's production standards at GlobalBank, here is a checklist for any sort program:

Before the SORT: - Validate that input files exist and are accessible - Log the start of the sort with timestamp - Set any required sort options (SORT-RETURN initialization)

In the INPUT PROCEDURE: - Validate every record before RELEASEing - Count records read, released, and rejected - Write rejected records to an error/exception file with reason codes - Implement an error threshold — abort if too many records fail - Handle AT END properly

In the OUTPUT PROCEDURE: - Handle the first record specially (initialize control break fields) - Count records written - Handle all edge cases (empty file, single record, etc.) - Verify expected record counts match actual counts

After the SORT: - Check SORT-RETURN - Display/log all record counts for reconciliation - Set appropriate RETURN-CODE for JCL condition checking - Log the end of the sort with timestamp and elapsed time

⚖️ The Human Factor: Priya Kapoor observes: "The sort itself rarely fails — our sort utility has been rock-solid for decades. What fails is the data. Bad records, truncated files, wrong file delivered to the wrong job — that is what brings the batch cycle down. Every line of validation in your INPUT PROCEDURE is insurance against a 3 AM phone call."


15.17 Multi-Key Sort Strategies

Sorting on multiple keys is common in production systems, but the interaction between keys, their data types, and their sort directions creates subtleties that deserve careful attention.

15.17.1 Key Priority and Declaration Order

When multiple sort keys are declared, the first key listed is the major key and the last is the minor key. The sort orders records by the major key first; only when two records have identical major keys does the minor key determine order:

           SORT SORT-FILE
               ON ASCENDING  KEY SR-REGION        *> Major
               ON ASCENDING  KEY SR-BRANCH        *> Intermediate
               ON DESCENDING KEY SR-AMOUNT         *> Minor
               USING TRANS-IN
               GIVING TRANS-OUT

This produces output grouped by region, then by branch within each region, and within each branch the transactions appear in descending amount order (largest first). Understanding this hierarchy is essential for downstream programs that rely on the sort order for control-break processing.

15.17.2 Mixed Ascending and Descending Keys

Mixing sort directions is powerful for reporting scenarios. For example, MedClaim needs claims sorted by provider (ascending) and then by charge amount (descending) to identify the highest-cost claims for each provider first:

           SORT SORT-FILE
               ON ASCENDING  KEY SC-PROVIDER-NUM
               ON DESCENDING KEY SC-CHARGE-AMT
               ON ASCENDING  KEY SC-CLAIM-DATE
               WITH DUPLICATES IN ORDER
               USING CLAIMS-IN
               GIVING CLAIMS-OUT

Within each provider, claims appear from most expensive to least expensive. When two claims for the same provider have the same charge amount, they are further ordered by claim date (ascending). WITH DUPLICATES IN ORDER ensures that if all three keys match, the original file order is preserved.

15.17.3 Sorting on Packed Decimal Keys

When a sort key is COMP-3 (packed decimal), the sort utility must know the field's representation to compare values correctly. COBOL communicates this through the SD record layout. A common mistake is declaring the sort key as PIC X (alphanumeric) in the SD when the actual data is COMP-3:

      *--- WRONG: Sort key declared as alphanumeric ---
       SD  SORT-WORK.
       01  SORT-REC.
           05  SR-ACCT-NUM   PIC X(10).
           05  SR-AMOUNT     PIC X(06).     *> WRONG for COMP-3!
           05  FILLER        PIC X(84).

      *--- CORRECT: Sort key declared with proper USAGE ---
       SD  SORT-WORK.
       01  SORT-REC.
           05  SR-ACCT-NUM   PIC X(10).
           05  SR-AMOUNT     PIC S9(9)V99 COMP-3.  *> Correct
           05  FILLER        PIC X(84).

If the sort key is declared as alphanumeric, the sort utility compares bytes left to right using the collating sequence, which is not the same as numeric comparison for packed decimal. Negative amounts, in particular, will sort incorrectly because the sign nibble (X'D' for negative) is a higher hex value than X'C' (positive). Derek Washington once spent a day debugging a sort that placed all negative transactions after positive ones because the SD declared the amount field as PIC X.

15.17.4 Composite Keys via Concatenation

Sometimes you need to sort on a key that does not exist as a single field in the record. You can construct composite keys in an INPUT PROCEDURE:

       SD  SORT-WORK.
       01  SORT-REC.
           05  SR-COMPOSITE-KEY  PIC X(18).
           05  SR-ORIG-DATA      PIC X(82).

       1000-BUILD-COMPOSITE-KEY.
           OPEN INPUT RAW-FILE
           PERFORM 1100-PROCESS-INPUT UNTIL EOF-INPUT
           CLOSE RAW-FILE.

       1100-PROCESS-INPUT.
           READ RAW-FILE INTO WS-INPUT-REC
               AT END SET EOF-INPUT TO TRUE
               NOT AT END
                   STRING WI-REGION   DELIMITED BY SIZE
                          WI-DATE     DELIMITED BY SIZE
                          INTO SR-COMPOSITE-KEY
                   MOVE WS-INPUT-REC TO SR-ORIG-DATA
                   RELEASE SORT-REC
           END-READ.

This creates a sort key by concatenating region and date, allowing the sort to order by both fields as a single comparison. The OUTPUT PROCEDURE then extracts the original data from SR-ORIG-DATA.

15.17.5 Performance Implications of Key Count and Position

📊 Sort Performance by Key Configuration:

Key Configuration Relative Time Notes
Single key at offset 0 1.00x (baseline) Optimal — sort utility may use fast path
Single key at offset 50 1.02x Minimal overhead for key extraction
Three ascending keys at offset 0 1.05x Key comparison slightly longer
Three mixed-direction keys 1.08x Descending keys may require transformation
Five keys with COMP-3 1.12x Packed decimal comparison adds cycles

These differences are negligible for small files but can be significant when sorting 50+ million records during the nightly batch window.


15.18 Sort Performance Benchmarking

In production environments, sort performance directly affects batch window completion. Understanding how to benchmark and optimize sorts is a practical skill.

15.18.1 Measuring Sort Elapsed Time

COBOL provides the ACCEPT FROM TIME facility for simple benchmarking:

       01  WS-START-TIME         PIC 9(08).
       01  WS-END-TIME           PIC 9(08).
       01  WS-ELAPSED            PIC 9(08).

           ACCEPT WS-START-TIME FROM TIME

           SORT SORT-FILE
               ON ASCENDING KEY SR-ACCT-NUM
               USING TRANS-IN
               GIVING TRANS-OUT

           ACCEPT WS-END-TIME FROM TIME
           COMPUTE WS-ELAPSED = WS-END-TIME - WS-START-TIME
           DISPLAY 'SORT ELAPSED: ' WS-ELAPSED ' HHMMSSCC'

For more precise measurement, DFSORT writes timing information to SYSOUT. The key metrics are:

  • INSERT time: Time to accept records into the sort (INPUT PROCEDURE or USING I/O)
  • SORT time: Time for the actual sort algorithm
  • MERGE time: Time to merge sorted runs
  • OUTPUT time: Time to return sorted records (OUTPUT PROCEDURE or GIVING I/O)

15.18.2 Estimating Sort Resource Requirements

Before running a large sort in production, estimate the resource requirements:

Records:         10,000,000
Record length:   200 bytes
Total data:      10M * 200 = 2 GB

Sort work space: Typically 2-3x the data size
                 Estimate: 4-6 GB across sort work datasets

Memory:          More memory = fewer merge passes
                 Ideal: enough to hold all data in memory
                 For 2 GB: request at least 512 MB

Estimated time:  DFSORT on z15: ~2 GB/min throughput
                 Estimate: ~1 minute for sort phase
                 Add I/O time for input and output

Maria Chen maintains a spreadsheet of sort performance baselines for every major batch job. When a sort takes 20% longer than its baseline, operations automatically investigates — it might indicate a data volume increase, an I/O contention issue, or a system configuration change.

15.18.3 DFSORT ICETOOL for Analysis

DFSORT's ICETOOL utility provides powerful analysis capabilities that complement COBOL's SORT:

//ANALYZE  EXEC PGM=ICETOOL
//TOOLMSG  DD SYSOUT=*
//DFSMSG   DD SYSOUT=*
//IN1      DD DSN=PROD.TRANS.SORTED,DISP=SHR
//TOOLIN   DD *
  COUNT FROM(IN1) EMPTY
  STATS FROM(IN1) ON(45,6,PD) TITLE('AMOUNT STATISTICS')
  UNIQUE FROM(IN1) ON(1,10,CH) TITLE('UNIQUE ACCOUNTS')
/*

This tells you: the file's record count, statistics (min, max, average, total) on the packed decimal amount field at offset 45, and the number of unique account numbers. These metrics help verify sort correctness and are invaluable for operations monitoring.

🧪 Try It Yourself: Create a sort program that processes at least 1,000 records with three sort keys (one descending). Add timing instrumentation using ACCEPT FROM TIME before and after the SORT. Run the program, then modify it to use INPUT PROCEDURE filtering (remove 30% of records) and compare the elapsed time. Is the filtered sort faster? By how much?


15.19 External Sort Integration: COBOL and JCL Working Together

In production mainframe environments, SORT operations rarely exist in isolation. They are part of multi-step JCL jobs where the sort interacts with preceding and following steps. Understanding this integration is essential for production COBOL developers.

15.19.1 Typical Production Sort Job Stream

A complete nightly batch cycle at GlobalBank might look like this:

//*=========================================================
//* STEP010: Extract today's transactions from DB2
//*=========================================================
//STEP010  EXEC PGM=GBEXT01
//RAWFILE  DD DSN=WORK.TXN.RAW.DAILY,DISP=(NEW,CATLG),
//            SPACE=(CYL,(100,20)),UNIT=SYSDA

//*=========================================================
//* STEP020: Sort and validate transactions (COBOL SORT)
//*=========================================================
//STEP020  EXEC PGM=GBSORT01,COND=(4,LT,STEP010)
//TXNIN    DD DSN=WORK.TXN.RAW.DAILY,DISP=SHR
//TXNOUT   DD DSN=WORK.TXN.SORTED.DAILY,DISP=(NEW,CATLG),
//            SPACE=(CYL,(100,20)),UNIT=SYSDA
//ERRFILE  DD DSN=WORK.TXN.ERRORS.DAILY,DISP=(NEW,CATLG),
//            SPACE=(CYL,(5,2)),UNIT=SYSDA
//SORTWK01 DD SPACE=(CYL,(50,10)),UNIT=SYSDA
//SORTWK02 DD SPACE=(CYL,(50,10)),UNIT=SYSDA
//SORTWK03 DD SPACE=(CYL,(50,10)),UNIT=SYSDA

//*=========================================================
//* STEP030: Apply sorted transactions to master file
//*=========================================================
//STEP030  EXEC PGM=GBUPD01,COND=(4,LT,STEP020)
//TXNIN    DD DSN=WORK.TXN.SORTED.DAILY,DISP=SHR
//MASTER   DD DSN=PROD.ACCT.MASTER,DISP=OLD

Key observations:

  1. SORTWK datasets: Three sort work datasets provide parallelism for the sort utility. More work datasets generally improve performance for large sorts.

  2. COND parameter: STEP030 runs only if STEP020 completed with a return code less than 4. This is the production safety net — if the sort fails or detects too many errors, downstream processing stops.

  3. Separate error file: The ERRFILE captures rejected records for manual review. Operations monitors check this file's record count each morning.

15.19.2 Coordinating Sort Return Codes with JCL

The RETURN-CODE special register (set by the COBOL program) flows to the JCL COND parameter. A well-designed sort program uses a tiered return code scheme:

      *--- Set return code based on results ---
           EVALUATE TRUE
               WHEN SORT-RETURN NOT = ZERO
                   MOVE 16 TO RETURN-CODE
                   DISPLAY 'SORT FAILED - RC 16'
               WHEN WS-ERROR-PCT > 5
                   MOVE 12 TO RETURN-CODE
                   DISPLAY 'HIGH ERROR RATE - RC 12'
               WHEN WS-ERROR-PCT > 1
                   MOVE 8 TO RETURN-CODE
                   DISPLAY 'MODERATE ERRORS - RC 8'
               WHEN WS-ERROR-CNT > 0
                   MOVE 4 TO RETURN-CODE
                   DISPLAY 'MINOR ERRORS - RC 4'
               WHEN OTHER
                   MOVE 0 TO RETURN-CODE
                   DISPLAY 'CLEAN RUN - RC 0'
           END-EVALUATE

Downstream JCL steps check these return codes:

//* Run only if sort was clean or had minor errors
//STEP030  EXEC PGM=GBUPD01,COND=(8,LE,STEP020)

This means: skip STEP030 if STEP020's return code is greater than or equal to 8. So the update runs only when the sort returned 0 or 4 (clean or minor errors).

15.19.3 Pre-Sort Verification with IDCAMS

Before running a COBOL sort, production jobs often verify the input file exists and is not empty:

//* Verify input file has records
//VERIFY   EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  PRINT INFILE(INDD) COUNT(1)
/*
//INDD     DD DSN=WORK.TXN.RAW.DAILY,DISP=SHR

If the file is empty or missing, IDCAMS returns a non-zero code and the sort step is skipped. This prevents the sort program from running against empty input and producing confusing results.


15.20 Chapter Summary

In this chapter, we explored the COBOL SORT and MERGE verbs — the workhorses of batch data processing. We covered:

  • The SORT verb with its SD entry, sort key specifications, and ASCENDING/DESCENDING options
  • USING and GIVING for simple, declarative sorts with no transformation
  • INPUT PROCEDURE with RELEASE for filtering and transforming records before sorting
  • OUTPUT PROCEDURE with RETURN for processing sorted records
  • The MERGE verb for combining pre-sorted files efficiently
  • Error handling through SORT-RETURN and the error threshold pattern
  • External sort utilities (DFSORT, SyncSort) and when to use JCL sort vs. COBOL SORT
  • Performance tuning including FASTSRT, sort work space allocation, and tag sorts
  • Common patterns including sort-match-update, sort-dedup, sort-split, and sort-aggregate

The SORT verb exemplifies COBOL's philosophy of declarative power: you specify what to sort and how to order it, and the runtime's sort utility — the product of decades of optimization — handles the rest. When you need more control, INPUT and OUTPUT PROCEDUREs let you inject your business logic into the sort pipeline without sacrificing the performance of the underlying sort engine.

In the next chapter, we turn to Report Writer — another declarative facility that lets COBOL handle the mechanics of page formatting, control breaks, and totaling while you focus on what the report should contain.


Key Terms Introduced in This Chapter

Term Definition
SD (Sort Description) A file description for sort work files, analogous to FD for regular files
SORT verb COBOL verb that arranges file records in a specified key order
MERGE verb COBOL verb that interleaves records from pre-sorted files
RELEASE Statement that passes a record to the sort within an INPUT PROCEDURE
RETURN Statement that retrieves a sorted record within an OUTPUT PROCEDURE
INPUT PROCEDURE User-written code that supplies records to the sort
OUTPUT PROCEDURE User-written code that processes sorted records
SORT-RETURN Special register containing the sort's return code
FASTSRT Compiler option allowing the sort utility to bypass COBOL I/O
Tag sort Technique of sorting keys separately from full records for performance
Stable sort A sort that preserves the relative order of records with equal keys
Sort work file Temporary file used by the sort utility during the sort process