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.
//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).
//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
| Statement | Purpose |
|---|---|
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 COPY | Copy mode — no sort, just transform |
SYMNAMES | Name 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.
| Format | Meaning |
|---|---|
CH | Character (EBCDIC) |
BI | Binary, unsigned |
FI | Fixed-point, signed (two's complement) |
PD | Packed decimal |
ZD | Zoned decimal |
FS | Floating sign — signed character numeric (e.g. -1234) |
CSF / UFF | Free-form signed/unsigned |
Y2C / Y2P / Y2Z | Two-digit year — char/packed/zoned with windowing |
D1 | One-byte hex |
AC | ASCII 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.
//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:
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.
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.
//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
| Operator | Meaning |
|---|---|
EQ | Equal |
NE | Not equal |
GT / GE | Greater than / greater or equal |
LT / LE | Less than / less or equal |
AND / OR | Combine conditions |
NOT | Negate the next condition |
Constants
| Form | Meaning |
|---|---|
C'string' | Character (EBCDIC) |
X'hex' | Hex bytes |
P'+1234' | Packed decimal |
Z'+1234' | Zoned decimal |
+1234 | Signed numeric constant |
Useful examples
(* 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.
(* 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
| Item | Meaning |
|---|---|
start,length | Copy 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 / DATE3 | Today's date in various formats |
TIME1 / TIME2 | Time of day |
SEQNUM,length,format | Record sequence number |
start,length,format,ADD,value,TO=format,LENGTH=n | Arithmetic |
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.
(* 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).
//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:
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
| Clause | Effect |
|---|---|
FNAMES=ddname[,ddname...] | Where to send the records |
INCLUDE=(cond) | Filter for this output only |
OMIT=(cond) | Drop matching for this output only |
SAVE | Catch-all — write records no other OUTFIL matched |
STARTREC=n,ENDREC=m | Numeric range of records to write |
SAMPLE=n | Take every nth record |
SPLIT | Round-robin across multiple FNAMES |
SPLIT1R=n | Split N per file before moving to the next |
HEADER1= / TRAILER1= | Per-file header/trailer record |
REMOVECC | Strip ANSI carriage-control byte |
NODETAIL | Suppress detail records (keep only header/trailer) |
SECTIONS=(field, ...) | Group by key, optional sub-header/trailer |
BUILD= / OVERLAY= | OUTREC-style reshape on this output only |
CONVERT | Switch 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.
//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.
//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:
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
| Option | Meaning |
|---|---|
UNPAIRED,F1 | Include F1 records with no F2 match (left outer) |
UNPAIRED,F2 | Include F2 records with no F1 match (right outer) |
UNPAIRED,F1,F2 | Full outer |
UNPAIRED,F1,ONLY | F1-only (anti-join) |
UNPAIRED,F2,ONLY | F2-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.
//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:
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.
//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.).
//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:
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
| Operator | What it does |
|---|---|
COPY | Copy records, optionally filtering / reshaping |
SORT | Sort with optional dedup |
SELECT | Pick records by occurrence count |
SPLICE | Combine records sharing a key |
OCCUR | Count and report occurrences |
STATS | Min/max/sum/avg of numeric fields |
RANGE | Count records in numeric ranges |
UNIQUE | Count distinct values |
MODE | Set ICETOOL behaviour (STOP, CONTINUE) |
DISPLAY | Format and report to TOOLMSG |
VERIFY | Validate decimal data |
RESIZE | Change record length |
DEFAULTS | Set 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.
//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.
//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:
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.
//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.
//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.
| Option | Effect |
|---|---|
COPY | Copy mode (no sort) — same as SORT FIELDS=COPY |
EQUALS | Preserve input order for tied keys (stable sort) |
NOEQUALS | Default — tied keys may be reordered |
STOPAFT=n | Stop after reading n records |
SKIPREC=n | Skip first n records |
DYNALLOC=(SYSDA,n) | Dynamic work-dataset allocation |
MAINSIZE=MAX | Use as much storage as JCL REGION allows |
RESALL=n | Reserve n bytes for system use |
Y2PAST=year | Two-digit-year window pivot |
ZDPRINT / NOZDPRINT | Print zoned-decimal output as numeric |
VLSCMP | Variable-length short-record comparison |
SPANINC=n | Spanned-record handling |
//SYSIN DD *
OPTION EQUALS,DYNALLOC=(SYSDA,8),MAINSIZE=MAX
SORT FIELDS=(1,8,CH,A)
/*
Output: (none — exits 0 on success)
Common pitfalls
- Forgetting
EQUALSwhen sort stability matters — DFSORT's default is NOEQUALS for performance. If you need first-occurrence semantics, setOPTION EQUALSor your "first row by key" will sometimes pick a different row. SUM FIELDS=NONEand ICETOOLSELECT 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.PDvsZDvsFS— 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 withC'00000123'always fails — useP'+123'.- 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=andOUTREC OVERLAY=know this;INCLUDE COND=(1,4,…)does not. OUTFIL SAVEand multipleINCLUDE— SAVE catches everything that didn't match anyINCLUDEOUTFIL. TwoOUTFIL FNAMES=with non-overlappingINCLUDEplus oneSAVEis a clean three-way split; with overlapping INCLUDEs, the same record is written twice (once to each match).OPTION COPYignoresSORT FIELDS=— you cannot have both. Choose one; the warning isICE128Iand easy to miss in TOOLMSG.JOINKEYSwithoutSORTED— DFSORT sorts each input automatically before joining. AddingSORTEDskips the implicit sort, but it lies if the input isn't actually sorted on the key — duplicates and missing joins will follow.- REFORMAT field positions are 1-based on the joined record —
F1:andF2:are field-reference prefixes, not numeric offsets. New-comers codeF1:1,80,F2:81,120and get garbage; the right form isF1:1,80,F2:1,120(each side is positioned within its own record). INREC OVERLAY=past the end of a fixed record — silently extends the record into the next OUTREC's territory. UseINREC BUILD=for any reshape that changes the record length.ICETOOL MODE STOPaborts the whole job on first error — useful for production.MODE CONTINUEkeeps running so you see all the errors; switch back to STOP for prod.MAINSIZE=MAXwithout REGION — DFSORT only sees the storage that JCLREGION=gave it. CodeREGION=0Mon the EXEC card to give the sort all available storage above the line.- DYNALLOC and
SYSDA— if the installation has changed the dynamic work pool fromSYSDAtoWORK, the job gets a B14 abend. Either omit DYNALLOC (defaults are installation-specific) or checkICEMAC/ICEPRMxxfor 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.
//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:
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.
//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:
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.
//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:
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.
//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:
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.
//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.
//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:
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.
//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)