cheat sheet

DFSORT

IBM DFSORT control statements and ICETOOL operators for sorting, merging, joining, and reshaping batch datasets on z/OS.

DFSORT — Sort, Merge, and ICETOOL

What it is

DFSORT is IBM's sort/merge/copy utility for z/OS — the engine behind nearly every batch transformation step that doesn't involve a database. It accepts a stream of records and emits them sorted, merged, copied, filtered, joined, reformatted, summarised, or any combination of those, driven by control statements supplied through SYSIN. ICETOOL is a higher-level driver that ships with DFSORT and adds named operators like COPY, SELECT, SPLICE, OCCUR, RANGE, and STATS — most production sorts these days are written as ICETOOL jobs because the operators wrap the common patterns more readably. Reach for DFSORT on z/OS batch; Syncsort is a drop-in alternative with the same control-statement syntax.

Install

DFSORT and ICETOOL ship with z/OS and run under the SORT/ICEMAN and ICETOOL program names. No installation is needed; if the site uses Syncsort instead, PGM=SORT is aliased and the same control statements work in the vast majority of cases. Run interactively from TSO option 6 with CALL 'SYS1.SICELINK(ICEMAN)', or submit through JCL with PGM=SORT or PGM=ICETOOL.

text
//STEP01   EXEC PGM=SORT

Output: (none — exits 0 on success)

Syntax

The base SORT invocation reads from SORTIN, writes to SORTOUT, and consumes control statements from SYSIN. Continuation onto the next line uses a non-blank in column 72 (most shops use a comma at the end of a line and continue indented on the next).

text
//STEP01   EXEC PGM=SORT
//SORTIN   DD DSN=...,DISP=SHR
//SORTOUT  DD DSN=...,DISP=(NEW,CATLG,DELETE),...
//SYSOUT   DD SYSOUT=*
//SYSIN    DD *
   SORT FIELDS=(start,length,format,A|D)
/*

Output: (none — exits 0 on success)

Essential control statements

StatementPurpose
SORT FIELDS=Define the sort key
MERGE FIELDS=Merge already-sorted inputs
INCLUDE COND=Keep only matching records
OMIT COND=Drop matching records
INREC BUILD= / OVERLAY=Reshape on the way in
OUTREC BUILD= / OVERLAY=Reshape on the way out
SUM FIELDS=Sum numeric fields, collapsing duplicate keys
OUTFIL FNAMES=Multiple outputs, each with its own filtering and reformatting
JOINKEYS F1= / F2=Sort-time join across SORTJNF1 and SORTJNF2
OPTION COPYCopy mode — no sort, just transform
SYMNAMESName your fields for readability

Field formats

A "field" in DFSORT is a (start, length, format) triplet. The format tells DFSORT how to interpret the bytes when comparing for sort, INCLUDE, OMIT, or arithmetic. The start position is 1-based and includes the 4-byte RDW for variable-length input.

FormatMeaning
CHCharacter (EBCDIC)
BIBinary, unsigned
FIFixed-point, signed (two's complement)
PDPacked decimal
ZDZoned decimal
FSFloating sign — signed character numeric (e.g. -1234)
CSF / UFFFree-form signed/unsigned
Y2C / Y2P / Y2ZTwo-digit year — char/packed/zoned with windowing
D1One-byte hex
ACASCII character

SORT — the basic sort

SORT FIELDS= lists one or more key fields and their direction (A ascending, D descending). The sort is stable only inside DFSORT's EQUALS option — by default tied records can be reordered.

text
//STEP01   EXEC PGM=SORT
//SORTIN   DD DSN=ALICE.RAW.DATA,DISP=SHR
//SORTOUT  DD DSN=ALICE.SORTED,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=80)
//SYSOUT   DD SYSOUT=*
//SYSIN    DD *
   SORT FIELDS=(1,8,CH,A,9,4,PD,D)
   OPTION EQUALS
/*

Output:

text
ICE000I 1 - CONTROL STATEMENTS FOR  5694-A01, Z/OS DFSORT V2R5
            SORT FIELDS=(1,8,CH,A,9,4,PD,D)
            OPTION EQUALS
ICE201I F RECORD TYPE IS F - DATA STARTS IN POSITION 1
ICE751I 1 - EFL-K06032: 0  IN: 184211  OUT: 184211
ICE054I 0 RECORDS - IN: 184211, OUT: 184211
ICE052I 0 END OF DFSORT

Multiple keys and direction

The classic three-key sort: by branch ascending, then transaction date descending, then sequence ascending.

text
SORT FIELDS=(1,3,CH,A,    (* branch code           *)
             4,8,PD,D,    (* transaction date      *)
             12,4,BI,A)   (* sequence number       *)

Output: (none — exits 0 on success)

INCLUDE / OMIT — filtering

INCLUDE COND= keeps only records that match a boolean expression; OMIT COND= drops them. Conditions chain with AND/OR and compare fields against constants or other fields.

text
//SYSIN    DD *
   SORT FIELDS=COPY
   INCLUDE COND=(1,3,CH,EQ,C'USA',AND,
                 4,8,PD,GE,P'20260101',AND,
                 4,8,PD,LE,P'20261231')
/*

Output: (none — exits 0 on success)

Comparison operators

OperatorMeaning
EQEqual
NENot equal
GT / GEGreater than / greater or equal
LT / LELess than / less or equal
AND / ORCombine conditions
NOTNegate the next condition

Constants

FormMeaning
C'string'Character (EBCDIC)
X'hex'Hex bytes
P'+1234'Packed decimal
Z'+1234'Zoned decimal
+1234Signed numeric constant

Useful examples

text
(* OR group *)
INCLUDE COND=(1,3,CH,EQ,C'USA',OR,1,3,CH,EQ,C'CAN',OR,1,3,CH,EQ,C'MEX')

(* Comparing two fields *)
INCLUDE COND=(15,5,FS,GT,20,5,FS)

(* Substring match *)
INCLUDE COND=(20,30,SS,EQ,C'INVALID')

(* Date range with a Y2 format *)
INCLUDE COND=(80,2,Y2C,GE,Y'70')      (* >= 1970 *)

(* Drop blanks *)
OMIT COND=(1,30,CH,EQ,C' ')

(* Drop dupes by key - really this is OUTFIL NODETAIL *)
SORT FIELDS=(1,10,CH,A)
SUM FIELDS=NONE                       (* keep only one record per key *)

Output: (none — exits 0 on success)

INREC / OUTREC — reshape

INREC reshapes the record before the sort; OUTREC reshapes after. Use INREC when the sort key is computed from existing fields and you want the new shape sorted; use OUTREC when you only want to reshape on the way out.

The two reshape verbs are:

  • BUILD= — start fresh, list everything you want in the output record.
  • OVERLAY= — keep the existing record and overlay specific positions.
text
(* Reformat: keep first 10 chars, add a constant, then the next 20 *)
INREC BUILD=(1,10,C' BATCH ',11,20)

(* Overlay: stamp a constant at position 60 *)
INREC OVERLAY=(60:C'PROCESSED ',70:DATE1,80:TIME1)

(* Reshape with arithmetic in OUTREC *)
OUTREC BUILD=(1,10,
              20,8,PD,ADD,P'+1',TO=PD,LENGTH=8,
              50,4,BI,DIV,+100,TO=ZD,LENGTH=10)

Output: (none — exits 0 on success)

BUILD vocabulary

ItemMeaning
start,lengthCopy bytes from input
start,length,format,EDIT=(...)Format numeric with an edit mask
C'string'Literal string
X'hex'Literal hex
nC' 'n blanks
nX'00'n hex zeros
DATE1 / DATE2 / DATE3Today's date in various formats
TIME1 / TIME2Time of day
SEQNUM,length,formatRecord sequence number
start,length,format,ADD,value,TO=format,LENGTH=nArithmetic
start,length,format,ZD,EDIT=(SI9T)Picture-based numeric edit

OVERLAY vs BUILD

OVERLAY is the better choice when you only need to change a small slice — it avoids the bug-prone exercise of listing every field that should stay the same.

text
(* BUILD - error-prone for long records *)
INREC BUILD=(1,40,        (* keep 1-40 *)
             X'00',       (* zero out 41 *)
             42,38)       (* keep 42-79 *)

(* OVERLAY - same result *)
INREC OVERLAY=(41:X'00')

Output: (none — exits 0 on success)

OUTFIL — multiple outputs and post-sort formatting

OUTFIL is the most-used DFSORT statement after SORT itself. It writes the sorted stream to one or more output ddnames, each with its own filter, reformatting, header/trailer, and special formatting (report headings, NODETAIL summary-only output, REMOVECC to strip carriage controls).

text
//SORTIN    DD DSN=ALICE.INPUT,DISP=SHR
//USA       DD DSN=ALICE.USA,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=80)
//CAN       DD DSN=ALICE.CAN,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=80)
//OTHER     DD DSN=ALICE.OTHER,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=80)
//SYSIN     DD *
   SORT FIELDS=COPY
   OUTFIL FNAMES=USA,INCLUDE=(1,3,CH,EQ,C'USA')
   OUTFIL FNAMES=CAN,INCLUDE=(1,3,CH,EQ,C'CAN')
   OUTFIL FNAMES=OTHER,SAVE
/*

Output:

text
ICE000I 1 -  CONTROL STATEMENTS FOR 5694-A01 DFSORT
ICE751I 1 - EFL-K06032: 0  IN: 14821  OUT: 14821
ICE253I 0 RECORDS SELECTED FROM INPUT FILE - USA: 8941, CAN: 4112, OTHER: 1768

Useful OUTFIL clauses

ClauseEffect
FNAMES=ddname[,ddname...]Where to send the records
INCLUDE=(cond)Filter for this output only
OMIT=(cond)Drop matching for this output only
SAVECatch-all — write records no other OUTFIL matched
STARTREC=n,ENDREC=mNumeric range of records to write
SAMPLE=nTake every nth record
SPLITRound-robin across multiple FNAMES
SPLIT1R=nSplit N per file before moving to the next
HEADER1= / TRAILER1=Per-file header/trailer record
REMOVECCStrip ANSI carriage-control byte
NODETAILSuppress detail records (keep only header/trailer)
SECTIONS=(field, ...)Group by key, optional sub-header/trailer
BUILD= / OVERLAY=OUTREC-style reshape on this output only
CONVERTSwitch RECFM (F to V)
VLTRIM=Trim trailing blanks on V records

SUM — collapse duplicates

SUM FIELDS= adds the named numeric fields across records with the same sort key and keeps a single result row. SUM FIELDS=NONE keeps the first record of each key group and discards the rest — the classic "dedupe by key, keep first" idiom.

text
//SYSIN    DD *
   SORT FIELDS=(1,8,CH,A)
   SUM FIELDS=(20,8,PD,28,8,PD)             (* sum totals and counts *)
/*

(* Dedupe by key, keep first *)
//SYSIN    DD *
   SORT FIELDS=(1,10,CH,A)
   SUM FIELDS=NONE
/*

Output: (none — exits 0 on success)

JOINKEYS — sort-time join

JOINKEYS joins two pre-sorted (or unsorted) inputs on a common key, producing an inner, left-outer, right-outer, or unpaired join. The two inputs are read through ddnames SORTJNF1 and SORTJNF2; control statements distinguish each side with JNF1CNTL / JNF2CNTL.

text
//STEP01    EXEC PGM=SORT,REGION=0M
//SORTJNF1  DD DSN=ALICE.CUSTOMERS,DISP=SHR
//SORTJNF2  DD DSN=ALICE.ORDERS,DISP=SHR
//SORTOUT   DD DSN=ALICE.CUST.WITH.ORDERS,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=200)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   JOINKEYS F1=SORTJNF1,FIELDS=(1,8,A),SORTED
   JOINKEYS F2=SORTJNF2,FIELDS=(11,8,A)
   JOIN UNPAIRED,F1                          (* left outer *)
   REFORMAT FIELDS=(F1:1,80,F2:1,120,
                    ?)                       (* ? = match indicator *)
   SORT FIELDS=COPY
/*

Output:

text
ICE000I 1 - CONTROL STATEMENTS FOR DFSORT JOINKEYS
ICE619I 0 JOINKEYS:  F1 RECORDS:    8230,  F2 RECORDS:   14881
ICE620I 0 JOINKEYS:  JOINED:        12947,  UNPAIRED-F1:    412
ICE054I 0 RECORDS - IN: 23111, OUT: 13359

JOIN options

OptionMeaning
UNPAIRED,F1Include F1 records with no F2 match (left outer)
UNPAIRED,F2Include F2 records with no F1 match (right outer)
UNPAIRED,F1,F2Full outer
UNPAIRED,F1,ONLYF1-only (anti-join)
UNPAIRED,F2,ONLYF2-only (anti-join)

The REFORMAT record contains F1: (fields from file 1) and F2: (fields from file 2); ? produces a match indicator B, 1, or 2 for paired, F1-only, F2-only.

MERGE — combine pre-sorted inputs

MERGE reads from SORTINnn ddnames (SORTIN01, SORTIN02, …) — each one already sorted on the merge key — and writes one combined stream. The control statement matches the sort syntax exactly.

text
//STEP01    EXEC PGM=SORT
//SORTIN01  DD DSN=ALICE.EAST.SORTED,DISP=SHR
//SORTIN02  DD DSN=ALICE.WEST.SORTED,DISP=SHR
//SORTIN03  DD DSN=ALICE.NORTH.SORTED,DISP=SHR
//SORTOUT   DD DSN=ALICE.ALL.SORTED,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=200)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   MERGE FIELDS=(1,8,CH,A)
/*

Output:

text
ICE000I 1 - CONTROL STATEMENTS FOR  DFSORT MERGE
ICE201I F RECORD TYPE IS F - DATA STARTS IN POSITION 1
ICE054I 0 RECORDS - IN: 142811, OUT: 142811

SYMNAMES — symbolic field names

SYMNAMES lets you name your fields and refer to them by name everywhere — sort, INCLUDE, INREC, OUTFIL, ICETOOL. The cost is one extra DD; the gain is control statements that read like documentation.

text
//SYMNAMES DD *
ID-CODE,1,8,CH
TXN-DATE,9,8,PD
AMOUNT,17,8,PD
COUNTRY,25,3,CH
*  comments start with asterisk
//SYSIN    DD *
   SORT FIELDS=(ID-CODE,A,TXN-DATE,D)
   INCLUDE COND=(COUNTRY,EQ,C'USA')
   OUTREC BUILD=(ID-CODE,2X,COUNTRY,2X,TXN-DATE,2X,AMOUNT)
/*

Output: (none — exits 0 on success)

ICETOOL — operator-driven sorting

ICETOOL drives DFSORT with named operators. The job allocates TOOLIN (operators), TOOLMSG (messages), DFSMSG (DFSORT messages), and a set of named DD pairs for each step (IN/OUT etc.).

text
//STEP01    EXEC PGM=ICETOOL,REGION=0M
//TOOLMSG   DD SYSOUT=*
//DFSMSG    DD SYSOUT=*
//IN        DD DSN=ALICE.RAW,DISP=SHR
//SORTED    DD DSN=ALICE.SORTED,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=80)
//TOOLIN    DD *
   SORT FROM(IN) TO(SORTED) USING(SRT1)
/*
//SRT1CNTL  DD *
   SORT FIELDS=(1,8,CH,A)
/*

Output:

text
ICE000I 1 - CONTROL STATEMENTS FOR  ICETOOL
ICE630I 0 MODE IN EFFECT:                  STOP
ICE606I 0 ICETOOL DFSORT CALL 01 USING SRT1CNTL.
ICE201I F RECORD TYPE IS F - DATA STARTS IN POSITION 1
ICE054I 0 RECORDS - IN: 142811, OUT: 142811

ICETOOL operators

OperatorWhat it does
COPYCopy records, optionally filtering / reshaping
SORTSort with optional dedup
SELECTPick records by occurrence count
SPLICECombine records sharing a key
OCCURCount and report occurrences
STATSMin/max/sum/avg of numeric fields
RANGECount records in numeric ranges
UNIQUECount distinct values
MODESet ICETOOL behaviour (STOP, CONTINUE)
DISPLAYFormat and report to TOOLMSG
VERIFYValidate decimal data
RESIZEChange record length
DEFAULTSSet fallback for missing operands

SELECT — first/last/duplicates by key

SELECT keeps the first, last, or all of records that share a key. The most common use: pick the highest-timestamped row for each customer.

text
//IN        DD DSN=ALICE.TRANS,DISP=SHR
//OUT       DD DSN=ALICE.LATEST,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=200)
//TOOLIN    DD *
   SELECT FROM(IN) TO(OUT) ON(1,8,CH) LAST USING(CTL1)
/*
//CTL1CNTL  DD *
   SORT FIELDS=(1,8,CH,A,9,14,CH,A)
/*

Output: (none — exits 0 on success)

OCCUR / RANGE / STATS — reports

These operators produce reports rather than transformed datasets — the output goes to TOOLMSG (or a DD named in LIST(...)). Useful for ad-hoc analysis or quick QA reports inside a pipeline.

text
//IN        DD DSN=ALICE.SALES,DISP=SHR
//RPT       DD SYSOUT=*
//TOOLIN    DD *
*  Top 10 most-frequent country codes
   OCCUR FROM(IN) LIST(RPT) ON(25,3,CH) ON(VALCNT) -
         PAGE HEADER('TOP COUNTRY CODES')

*  Distribution into ranges
   RANGE FROM(IN) ON(17,8,PD) -
         LOWER(0,100,1000,10000,100000) -
         HIGHER(100,1000,10000,100000,999999999)

*  Stats on the amount field
   STATS FROM(IN) ON(17,8,PD)
/*

Output:

text
TOP COUNTRY CODES                                              PAGE     1
       VALUE     COUNT
        USA      8941
        CAN      4112
        MEX      1421
        GBR       347
        ...

ICE628I 0 RECORDS COUNTED IN EACH RANGE OF (17,8,PD):
        LOW    HIGH       COUNT
          0     100       12381
        100    1000        9412
       1000   10000        2114
      10000  100000         418
     100000 999999999        12

ICE628I 0 STATS FOR (17,8,PD):  N=24337  MIN=00000000   MAX=00432110
         SUM=00037891240  AVG=00001557

SPLICE — combine partial records

SPLICE joins records that share a key, taking fields from each. The use case is "consolidate two-row records into one" — e.g. one header row and one detail row per transaction.

text
//IN        DD DSN=ALICE.PARTIAL,DISP=SHR
//OUT       DD DSN=ALICE.WHOLE,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=200)
//TOOLIN    DD *
   SPLICE FROM(IN) TO(OUT) ON(1,8,CH) -
          WITH(81,80) -                              (* take cols 81-160 from second row *)
          USING(SPL1)
/*
//SPL1CNTL  DD *
   SORT FIELDS=(1,8,CH,A,9,1,CH,A)               (* key + row-type *)
/*

Output: (none — exits 0 on success)

COPY — transform without sort

COPY is for filtering and reshaping records without changing their order. Cheaper than SORT because no comparison work is done.

text
//IN        DD DSN=ALICE.RAW,DISP=SHR
//OUT       DD DSN=ALICE.USA,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),DCB=(RECFM=FB,LRECL=80)
//TOOLIN    DD *
   COPY FROM(IN) TO(OUT) USING(CP1)
/*
//CP1CNTL   DD *
   INCLUDE COND=(25,3,CH,EQ,C'USA')
   OUTREC BUILD=(1,40,SEQNUM,8,ZD,42:50,40)
/*

Output: (none — exits 0 on success)

OPTION — runtime tuning

OPTION sets DFSORT runtime behaviour — useful when the defaults don't fit the job. Some options are best put in the installation defaults; others belong on the SYSIN of a specific step.

OptionEffect
COPYCopy mode (no sort) — same as SORT FIELDS=COPY
EQUALSPreserve input order for tied keys (stable sort)
NOEQUALSDefault — tied keys may be reordered
STOPAFT=nStop after reading n records
SKIPREC=nSkip first n records
DYNALLOC=(SYSDA,n)Dynamic work-dataset allocation
MAINSIZE=MAXUse as much storage as JCL REGION allows
RESALL=nReserve n bytes for system use
Y2PAST=yearTwo-digit-year window pivot
ZDPRINT / NOZDPRINTPrint zoned-decimal output as numeric
VLSCMPVariable-length short-record comparison
SPANINC=nSpanned-record handling
text
//SYSIN    DD *
   OPTION EQUALS,DYNALLOC=(SYSDA,8),MAINSIZE=MAX
   SORT FIELDS=(1,8,CH,A)
/*

Output: (none — exits 0 on success)

Common pitfalls

  1. Forgetting EQUALS when sort stability matters — DFSORT's default is NOEQUALS for performance. If you need first-occurrence semantics, set OPTION EQUALS or your "first row by key" will sometimes pick a different row.
  2. SUM FIELDS=NONE and ICETOOL SELECT FIRST — both look like dedupe, but the former requires a tie-breaking sort spec, the latter takes the first record of each ON-key after sorting on a secondary key. Mixing them up produces wrong "latest record" output.
  3. PD vs ZD vs FS — packed (PD) is 2 digits per byte plus sign; zoned (ZD) is 1 digit per byte; free-form signed (FS) is '-1234 '. Comparing a packed field with C'00000123' always fails — use P'+123'.
  4. Variable-length records and the 4-byte RDW — for VB input, the first 4 bytes of every record are the Record Descriptor Word; field positions start at 5, not 1. OUTFIL VLTRIM= and OUTREC OVERLAY= know this; INCLUDE COND=(1,4,…) does not.
  5. OUTFIL SAVE and multiple INCLUDE — SAVE catches everything that didn't match any INCLUDE OUTFIL. Two OUTFIL FNAMES= with non-overlapping INCLUDE plus one SAVE is a clean three-way split; with overlapping INCLUDEs, the same record is written twice (once to each match).
  6. OPTION COPY ignores SORT FIELDS= — you cannot have both. Choose one; the warning is ICE128I and easy to miss in TOOLMSG.
  7. JOINKEYS without SORTED — DFSORT sorts each input automatically before joining. Adding SORTED skips the implicit sort, but it lies if the input isn't actually sorted on the key — duplicates and missing joins will follow.
  8. REFORMAT field positions are 1-based on the joined recordF1: and F2: are field-reference prefixes, not numeric offsets. New-comers code F1:1,80,F2:81,120 and get garbage; the right form is F1:1,80,F2:1,120 (each side is positioned within its own record).
  9. INREC OVERLAY= past the end of a fixed record — silently extends the record into the next OUTREC's territory. Use INREC BUILD= for any reshape that changes the record length.
  10. ICETOOL MODE STOP aborts the whole job on first error — useful for production. MODE CONTINUE keeps running so you see all the errors; switch back to STOP for prod.
  11. MAINSIZE=MAX without REGION — DFSORT only sees the storage that JCL REGION= gave it. Code REGION=0M on the EXEC card to give the sort all available storage above the line.
  12. DYNALLOC and SYSDA — if the installation has changed the dynamic work pool from SYSDA to WORK, the job gets a B14 abend. Either omit DYNALLOC (defaults are installation-specific) or check ICEMAC/ICEPRMxx for the local convention.

Sources

References consulted while writing this article. Links open in a new tab.

  • IBM Documentation — DFSORT Application Programming Guide — Authoritative reference for SORT, MERGE, ICETOOL, and SYMNAMES syntax used in this article.

Real-world recipes

Dedupe on a composite key, keeping the latest record

Common requirement: given a transaction file with multiple rows per (customer, account), keep only the row with the highest timestamp.

text
//STEP01    EXEC PGM=ICETOOL,REGION=0M
//TOOLMSG   DD SYSOUT=*
//DFSMSG    DD SYSOUT=*
//IN        DD DSN=ALICE.ALL.TRANS,DISP=SHR
//OUT       DD DSN=ALICE.LATEST,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=200)
//TOOLIN    DD *
   SELECT FROM(IN) TO(OUT) ON(1,8,CH) ON(9,8,CH) LAST USING(C1)
/*
//C1CNTL    DD *
   SORT FIELDS=(1,8,CH,A,9,8,CH,A,17,14,CH,A)   (* cust, acct, timestamp asc - LAST is highest *)
/*

Output:

text
ICE630I 0 MODE IN EFFECT:                  STOP
ICE606I 0 ICETOOL DFSORT CALL 01 USING C1CNTL.
ICE054I 0 RECORDS - IN: 184211, OUT: 38211
ICE602I 0 OPERATION RETURN CODE:  00

Three-way split with a catch-all

Route by country code into per-country output datasets and keep "everything else" in a fourth.

text
//STEP01    EXEC PGM=SORT
//SORTIN    DD DSN=ALICE.GLOBAL,DISP=SHR
//USA       DD DSN=ALICE.USA,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),DCB=(RECFM=FB,LRECL=200)
//CAN       DD DSN=ALICE.CAN,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),DCB=(RECFM=FB,LRECL=200)
//MEX       DD DSN=ALICE.MEX,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),DCB=(RECFM=FB,LRECL=200)
//OTHER     DD DSN=ALICE.OTHER,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),DCB=(RECFM=FB,LRECL=200)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   SORT FIELDS=COPY
   OUTFIL FNAMES=USA,INCLUDE=(25,3,CH,EQ,C'USA')
   OUTFIL FNAMES=CAN,INCLUDE=(25,3,CH,EQ,C'CAN')
   OUTFIL FNAMES=MEX,INCLUDE=(25,3,CH,EQ,C'MEX')
   OUTFIL FNAMES=OTHER,SAVE
/*

Output:

text
ICE253I 0 RECORDS SELECTED - USA: 8941, CAN: 4112, MEX: 1421, OTHER: 1768

Inner join two files and pick a few columns

The classic "enrich orders with customer details" pattern — joining on customer id, keeping the order plus name and country from the customer record.

text
//STEP01    EXEC PGM=SORT,REGION=0M
//SORTJNF1  DD DSN=ALICE.ORDERS,DISP=SHR
//SORTJNF2  DD DSN=ALICE.CUSTOMERS,DISP=SHR
//SORTOUT   DD DSN=ALICE.ORDERS.ENRICHED,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=150)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   JOINKEYS F1=SORTJNF1,FIELDS=(11,8,A)
   JOINKEYS F2=SORTJNF2,FIELDS=(1,8,A)
   REFORMAT FIELDS=(F1:1,100,F2:9,40,F2:49,3)
   SORT FIELDS=(1,8,CH,A)
/*

Output:

text
ICE619I 0 JOINKEYS:  F1 RECORDS:    14881,  F2 RECORDS:    8230
ICE620I 0 JOINKEYS:  JOINED:        14881
ICE054I 0 RECORDS - IN: 23111, OUT: 14881

Sum totals by group with a heading

Produce a one-page summary report: a heading, then one detail row per branch with the total of the amount field.

text
//STEP01    EXEC PGM=SORT
//SORTIN    DD DSN=ALICE.TXN,DISP=SHR
//SORTOUT   DD SYSOUT=*
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   SORT FIELDS=(1,3,CH,A)
   SUM FIELDS=(17,8,PD)
   OUTFIL REMOVECC,
          HEADER1=(' BRANCH SUMMARY - ',DATE1,/,
                   ' ================ ',/,
                   ' BR    TOTAL          '),
          BUILD=(1,3,2X,17,8,PD,EDIT=(SIIII,IIT.TT))
/*

Output:

text
 BRANCH SUMMARY - 2026/05/25
 ================
 BR    TOTAL
 USA       89,234,158.00
 CAN       42,118,442.00
 MEX       14,098,712.00
 GBR        3,476,118.00

Generate a numbered-line copy of a file

Replace the first 8 bytes of each record with a 1-up sequence number, padded with leading zeros — useful when ingest expects ordered IDs.

text
//STEP01    EXEC PGM=SORT
//SORTIN    DD DSN=ALICE.ORIGINAL,DISP=SHR
//SORTOUT   DD DSN=ALICE.NUMBERED,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=FB,LRECL=80)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   OPTION COPY
   INREC OVERLAY=(1:SEQNUM,8,ZD)
/*

Output: (none — exits 0 on success)

Distribution report on a numeric field

Show how many records fall in each amount range, without producing any transformed dataset.

text
//STEP01    EXEC PGM=ICETOOL
//TOOLMSG   DD SYSOUT=*
//DFSMSG    DD SYSOUT=*
//IN        DD DSN=ALICE.SALES,DISP=SHR
//RPT       DD SYSOUT=*
//TOOLIN    DD *
   RANGE FROM(IN) ON(17,8,PD) -
         LOWER(0,100,1000,10000,100000) -
         HIGHER(100,1000,10000,100000,999999999) -
         BLANK -
         PAGE HEADER('AMOUNT DISTRIBUTION') DATE(YMD) TIME
/*

Output:

text
AMOUNT DISTRIBUTION                                  2026/05/25 14:32:11

  LOW       HIGH       COUNT
       0      100      12381
     100     1000       9412
    1000    10000       2114
   10000   100000        418
  100000 999999999        12

Convert FB to VB without breaking downstream readers

Some downstream consumers expect VB; use OUTFIL CONVERT to add the RDW and trim trailing blanks in one step.

text
//STEP01    EXEC PGM=SORT
//SORTIN    DD DSN=ALICE.FB80,DISP=SHR
//SORTOUT   DD DSN=ALICE.VB80,DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(20,5)),DCB=(RECFM=VB,LRECL=84)
//SYSOUT    DD SYSOUT=*
//SYSIN     DD *
   OPTION COPY
   OUTFIL CONVERT,VLTRIM=C' '
/*

Output: (none — exits 0 on success)