DFSORT Tricks PDF
DFSORT Tricks PDF
DFSORT Tricks PDF
DFSORT Web Site For papers, online books, news, tips, examples and more, visit the DFSORT website at URL: http://www.ibm.com/storage/dfsort
ii
Contents
Smart DFSORT Tricks 1 Introduction: Details of functions used in tricks 1 Find and extract values from different positions 1 Extract and justify delimited fields 2 Squeeze out blanks or other characters 4 Add leading and trailing apostrophes 4 Deconstruct and reconstruct CSV records 5 Only include records with today's date 6 Include records using relative dates 6 Fields from different record types 7 Create files with matching and non-matching records 9 Split a file to n output files dynamically 15 Five ways to split a data set 16 Sum a number with a decimal point 21 Check for a numeric string 23 Change a C sign to an F sign in PD values 24 Display the number of input or output records 24 Display SMF, TOD and ETOD date and time in readable form 25 Include or omit groups of records 26 Sort groups of records 29 Set RC=12 or RC=4 if file is empty, has more than n records, etc 31 Delete all members of a PDS 33 Keep dropped duplicate records (XSUM) 35 Create DFSORT Symbols from COBOL COPYs 36 Join fields from two files on a key 43 Join fields from two files record-by-record 44 VB to FB conversion 45 FB to VB conversion 46 Sort records between a header and trailer 47 Keep the last n records 48 Sample records 50 Change all zeros in your records to spaces 50 Insert date and time of run into records 51 Change uppercase to lowercase or lowercase to uppercase 52 RACF "SPECIAL" report with and without DFSORT symbols 53 Multiple output records from some (but not all) input records 56 Replace leading spaces with zeros 57 Generate JCL to submit to the internal reader 58 Totals by key and grand totals 61 Omit data set names with Axxx. as the high level qualifier 62 Dataset counts and space by high level qualifier 63 Delete duplicate SMF records 64 Sort ddmonyy dates 65 Turn cache on for all volumes 65 C/C++ calls to DFSORT and ICETOOL 67 REXX calls to DFSORT and ICETOOL 69 Pull records from a Master file 69 Concurrent VSAM/non-VSAM load 70 DCOLLECT conversion reports 72
Contents
iii
iv
//S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file (FB/n) //SORTOUT DD DSN=... output file (FB/8) //SYSIN DD OPTION COPY INREC PARSE=(%=(ENDAT=C'COPY'), % =(STARTAFT=BLANKS,FIXLEN=8)), BUILD=(% ) / Here's how it works: ENDAT=C'COPY' finds the end of the 'COPY' string in each record. % is used because we don't need to extact anything at this point. STARTAFT=BLANKS finds the next non-blank character after 'COPY'. FIXLEN=8 extracts the 8 bytes starting at that non-blank into the %00 fixed parsed field. BUILD creates an output record with the 8-bytes we extracted into %00. With the job above, if 'COPY' is not found in a record, the output record will contain blanks. If you don't want blank output records when the input doesn't have 'COPY', you can use DFSORT's substring search feature to only keep the records with 'COPY' in them: INCLUDE COND=(1,n,SS,EQ,C'COPY')
//S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SYMNAMES DD Fld1,1,4 Fld2,% Fld3,% 1 Fld4,% 2 Semi,';' Blank,' ' //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD OPTION COPY Extract second field into % . INREC PARSE=(Fld2=(ABSPOS=5,FIXLEN=2,ENDBEFR=Semi), Extract third field into % 1. Fld3=(FIXLEN=4,ENDBEFR=Semi), Extract fourth field into % 2. Fld4=(FIXLEN=5,ENDBEFR=Blank)), Create output record with first field, semicolon, right-justified % field, semicolon,% 1 field, semicolon and right-justified % 2 field. BUILD=(Fld1,Fld2,JFY=(SHIFT=RIGHT),Semi, Fld3,Semi,Fld4,JFY=(SHIFT=RIGHT)) / We are using DFSORT Symbols for the fields and constants here as follows: Fld1: positions 1-4 - first field - fixed length Fld2: %00 - second field - variable length delimited by a semicolon Fld3: %01 - third field - variable length delimited by a semicolon Fld4: %02 - fourth field - variable length delimited by a blank Semi: constant for semicolon (';') Blank: constant for blank (' ') You can use Symbols for fixed fields (p,m), parsed fields (%nn) and constants to make your DFSORT and ICETOOL control statements easier to code and understand. Here's how the DFSORT job works: %00 extracts the 2-byte value that starts at position 5 and ends before the next semicolon. %00 extracts '1 ' for the first record, '25' for the second record, and so on. %01 extracts the 4-byte value that starts after the semicolon and ends before the next semicolon. %01 extracts 'A2 ' for the first record, 'A ' for the second record, and so on. %02 extracts the 5-byte value that starts after the semicolon and ends before the next blank. %02 extracts '13.2 ' for the first record, '2.0 ' for the second record, and so on. BUILD creates an output record for each input record with input positions 1-4 ('AAA;'), the value in %00 right-justified (e.g. ' 1'), a semicolon, the value in %01 (e.g. 'A2 '), a semicolon, and the value in %02 rightjustified (e.g. ' 13.2').
where each of the three fields is 16 bytes. I want to remove all but one space between the fields so the output records look like this: Kevin James R Douglas Philips K Smith John L Can I use DFSORT to do this? I have some input data like this: abcd!efghi!jklm! ! abcdefgh!ijklmnopq! ! I need to remove the ! characters and shift the remaining characters to the left so the output records look like this: abcdefghijklm abcdefghijklmnopq Can I use DFSORT to do this? Using DFSORT's SQZ function, we can "squeeze out" blanks or other characters. SQZ removes blanks automatically. MID=C' ' can be used to insert one blank for each group of blanks SQZ removes. PREBLANK=list can be used to remove one or more other characters as well. These control statements would satisfy the first requirement: OPTION COPY INREC BUILD=(1,48,SQZ=(SHIFT=LEFT,MID=C' ')) These control statements would satisfy the second requirement: OPTION COPY INREC OVERLAY=(1,25,SQZ=(SHIFT=LEFT,PREBLANK=C'!'))
How can I do that? You can do this quite easily with DFSORT's JFY function as follows: OPTION COPY INREC BUILD=(1,13,JFY=(SHIFT=LEFT,LEAD=C'NUMBER ''', TRAIL=C'''')) SHIFT=LEFT left-justifies the data. LEAD inserts NUMBER ' before the value with no intervening blanks. TRAIL inserts ' after the value with no intervening blanks.
BUILD creates an output record for each input record with the value in %03, a comma, the value in %02, a comma, the value in %01, a comma and the value in %00. At this point, the records would look like this: 15,2 ,"1,23" ,"x@yz.e,q@yz.e" 1 , ,"56 131,44","_ansingh@sympatico.ca" The OUTREC statement uses SQZ to remove the blanks between the fields. PAIR=QUOTE ensures that blanks within quoted strings are not removed.
INCLUDE ONLY V-TYPE RECORDS AND D-TYPE RECORDS INCLUDE COND=(9,2,CH,EQ,C'V ',OR,9,2,CH,EQ,C'D ') CREATE REFORMATTED V-TYPE RECORDS INREC IFTHEN=(WHEN=(9,2,CH,EQ,C'V '), - just V-type records BUILD=(1,4, - RDW 5:29,6, - DCVVOLSR - volser (for sorting) 11:9,2, - DCURCTYP - record type (for sorting) 13:44X, - blank dsname (for report) 57:29,6, - DCVVOLSR - volser (for report) 63:35,1, - DCVFLAG1 - volume type flags Translate Volume type flag to Description for report, using lookup and change CHANGE=(7,B'..1.....',C'PRIVATE', B'...1....',C'PUBLIC', B'....1...',C'STORAGE'), 7 :7X)), - blank create date field (for report) CREATE REFORMATTED D-TYPE RECORDS IFTHEN=(WHEN=(9,2,CH,EQ,C'D '), - just D-type records BUILD=(1,4, - RDW 5:83,6, - DCDVOLSR - volser (for sorting) 11:9,2, - DCURCTYP - record type (for sorting) 13:29,44, - DCDDSNAM - dsname (for sorting/report) 57:6X, - blank volser field (for report) 63:7X, - blank volume type field (for report) 7 :1 9,4,PD,M11)) - DCDCREDT - create date (for report) SORT ON VOLSER (ASCENDING), RECORD TYPE (DESCENDING -> V,D) AND DSNAME (ASCENDING) SORT FORMAT=CH, FIELDS=(5,6,A, - volser in ascending order 11,2,D, - record type in descending order (V, D) 13,44,A) - dsname in ascending order PRINT REPORT USING FIELDS FROM REFORMATTED V-TYPE AND D-TYPE RECORDS OUTFIL FNAMES=REPORT,VTOF, HEADER2=('Volume/Dataset Report', 3 :DATE=(MDY/),45:'Page ',PAGE=(M11,LENGTH=3),/,X,/, 'Volume',1 :'Type',2 :'Creation Date',36:'Data Set',/, 6C'-',1 :7C'-',2 :13C'-',36:44C'-'), OUTREC=(57,6,1 :63,7,2 :7 ,7,36:13,44) / Notice that OUTFIL's lookup and change feature was used to change the flags in DCVFLAG1 to English descriptions (PRIVATE, PUBLIC, STORAGE), and OUTFIL's VTOF feature was used to produce an FBA report data set rather than a VBA report data set. Here's an example of what a portion of the output from the report might look like. Notice that the volumes are in sorted order, and for each volume the fields from the V-record are followed by the fields from the D-records for that volume including the dsnames in sorted order.
2/23/ 5
Page
... SYS 2 STORAGE 2 2 2 2 2 2 ... 4276 5 48 5 48 5 48 5 48 5 42 DFPXA.ADEPT.OLD.TESTCASE HSMACT.H3.BAKLOG.D96 48.T HSMACT.H3.CMDLOG.D96 48.T HSMACT.H3.DMPLOG.D96 48.T HSMACT.H3.MIGLOG.D96 48.T SYS1.VTOCIX.SYS 2 44559 44558 44559 44559
identifier of '22' to the File2 records, and splice the second id byte for matching records so we get '12' for names in both files, '11' for names only in File1 and '22' for names only in File2. //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN1 DD DSN=... input File1 //IN2 DD DSN=... input File2 //T1 DD DSN=&&T1,UNIT=SYSDA,SPACE=(TRK,(5,5)) // USE MOD FOR T1 // DISP=(MOD,PASS) //OUT12 DD SYSOUT= names in File1 and File2 //OUT1 DD SYSOUT= names in File1 only //OUT2 DD SYSOUT= names in File2 only //TOOLIN DD Add '11' identifier for FILE1 records. COPY FROM(IN1) TO(T1) USING(CTL1) Add '22' identifier for FILE2 records. COPY FROM(IN2) TO(T1) USING(CTL2) SPLICE to match up records and write them to their appropriate output files. SPLICE FROM(T1) TO(OUT12) ON(1,1 ,CH) WITH(13,1) USING(CTL3) KEEPNODUPS / //CTL1CNTL DD Mark FILE1 records with '11' INREC OVERLAY=(12:C'11') / //CTL2CNTL DD Mark FILE2 records with '22' INREC OVERLAY=(12:C'22') / //CTL3CNTL DD Write matching records to OUT12 file. Remove id. OUTFIL FNAMES=OUT12,INCLUDE=(12,2,CH,EQ,C'12'),BUILD=(1,1 ) Write FILE1 only records to OUT1 file. Remove id. OUTFIL FNAMES=OUT1,INCLUDE=(12,2,CH,EQ,C'11'),BUILD=(1,1 ) Write FILE2 only records to OUT2 file. Remove id. OUTFIL FNAMES=OUT2,INCLUDE=(12,2,CH,EQ,C'22'),BUILD=(1,1 ) / The output files will have the following records: OUT12 Carrie Holly Vicky OUT1 Frank David OUT2 Karen Mary
10
You can use variations of the example above as appropriate. For example, consider this case: Input File1 1 15 5 8 1 1 Vicky Frank Carrie Holly David O1 O2 O1 O1 O2
The names appear in different columns in each file. We want two output files: the records from File1 that have a match in File2 the records from File1 that don't have a match in File2 Here's a variation of the previous SPLICE example that handles this case:
11
//S2 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN1 DD DSN=&&I1,DISP=(OLD,PASS) //IN2 DD DSN=&&I2,DISP=(OLD,PASS) //T1 DD DSN=&&TQ,UNIT=SYSDA,SPACE=(TRK,(5,5)), // USE MOD FOR T2 // DISP=(MOD,PASS) //OUT1 DD DSN=... names in FILE1 with match in FILE2 //OUT2 DD DSN=... names in FILE1 without match in FILE2 //TOOLIN DD Add '11' identifier for FILE1 records. COPY FROM(IN1) TO(T1) USING(CTL1) Put FILE2 name in same positions as FILE1 name. Add '22' identifier for FILE2 records. COPY FROM(IN2) TO(T1) USING(CTL2) SPLICE to match up records and write FILE1 records to their appropriate output files. SPLICE FROM(T1) TO(OUT1) ON(6,1 ,CH) WITH(22,1) USING(CTL3) KEEPNODUPS / //CTL1CNTL DD Mark FILE1 records with '11'. INREC OVERLAY=(21:C'11') / //CTL2CNTL DD Move FILE1 name field. Mark FILE2 records with '22'. INREC BUILD=(6:3,1 ,21:C'22') / //CTL3CNTL DD Write matching FILE1 records to OUT1 file. Remove id. OUTFIL FNAMES=OUT1,INCLUDE=(21,2,CH,EQ,C'12'),BUILD=(1,2 ) Write non-matching FILE1 records to OUT2 file. Remove id. OUTFIL FNAMES=OUT2,INCLUDE=(21,2,CH,EQ,C'11'),BUILD=(1,2 ) / The output files will have the following records: OUT1 5 Carrie 8 Holly 1 Vicky OUT2 1 1 David 15 Frank The examples above do not have any duplicates within either of the input files. When the input files have duplicates, things get trickier, but we can still use DFSORT's ICETOOL to create the output files for the names in File1 and File2, the names only in File1 and the names only in File2. Let's say the two input files contain these names: Input File1
12
Vicky Vicky David Frank Frank Frank Karen Mary Mary Input File2 Vicky Vicky Holly Carrie Carrie Frank Karen
1 2 1 1 2 3 1 1 2
3 4 1 1 2 4 2
Below is an ICETOOL job that can do this more complex match using ICETOOL's SELECT and SPLICE operators. First we add an identifier of 'DD' to one record for each name found in File1 and File2 (FIRSTDUP), add an identifier of 'UU' to one record for each name found only in File1 or only in File2 (NODUPS), add an identifier of '11' to the File1 records and add an identifier of '22' to the File2 records. Then we splice the second id byte for matching records so we get '1U' for names only in File1, '2U' for names only in File2, and an id other than '1U' or '2U' for names in both File and File2.
13
//S3 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN1 DD DSN=... input File1 //IN2 DD DSN=... input File2 //F1 DD DSN=&&F1,UNIT=SYSDA,SPACE=(CYL,(5,5)), // USE MOD FOR F1 // DISP=(MOD,PASS) //T1 DD DSN=&&TX,UNIT=SYSDA,SPACE=(CYL,(5,5)), // USE MOD FOR T1 // DISP=(MOD,PASS) //OUT12 DD SYSOUT= names in File1 and File2 //OUT1 DD SYSOUT= names in File1 only //OUT2 DD SYSOUT= names in File2 only //TOOLIN DD Get first record with each name from FILE1. SELECT FROM(IN1) TO(F1) ON(1,1 ,CH) FIRST Get first record with each name from FILE2. SELECT FROM(IN2) TO(F1) ON(1,1 ,CH) FIRST Get one record with each name in FILE1 and FILE2 and add 'DD' identifier. SELECT FROM(F1) TO(T1) ON(1,1 ,CH) FIRSTDUP USING(CTL1) Get one record with each name only in FILE1 or only in FILE2 and add 'UU' identifier. SELECT FROM(F1) TO(T1) ON(1,1 ,CH) NODUPS USING(CTL2) Add '11' identifier for FILE1 records. COPY FROM(IN1) TO(T1) USING(CTL3) Add '22' identifier for FILE2 records. COPY FROM(IN2) TO(T1) USING(CTL4) SPLICE to match up records and write them to their appropriate output files. SPLICE FROM(T1) TO(OUT1) ON(1,1 ,CH) WITHALL WITH(1,21) USING(CTL5) / //CTL1CNTL DD Mark records with FILE1/FILE2 match with 'DD'. OUTFIL FNAMES=T1,OVERLAY=(21:C'DD') / //CTL2CNTL DD Mark records without FILE1/FILE2 match with 'UU'. OUTFIL FNAMES=T1,OVERLAY=(21:C'UU') / //CTL3CNTL DD Mark FILE1 records with '11'. OUTFIL FNAMES=T1,OVERLAY=(21:C'11') / //CTL4CNTL DD Mark FILE2 records with '22'. OUTFIL FNAMES=T1,OVERLAY=(21:C'22') /
14
//CTL5CNTL DD Write FILE1 only records to OUT1 file. Remove id. OUTFIL FNAMES=OUT1,INCLUDE=(21,2,CH,EQ,C'1U'), BUILD=(1,2 ) Write FILE2 only records to OUT2 file. Remove id. OUTFIL FNAMES=OUT2,INCLUDE=(21,2,CH,EQ,C'2U'), BUILD=(1,2 ) Write matching records to OUT12 file. Remove id. OUTFIL FNAMES=OUT12,SAVE, BUILD=(1,2 ) / The output files will have the following records: OUT12 Frank Frank Frank Frank Karen Karen Vicky Vicky Vicky Vicky OUT1 David Mary Mary OUT2 Carrie Carrie Holly 1 2 1 1 1 2 1 2 3 4 1 2 1 2 3 4
15
//S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... input file //T1 DD DSN=&&T1,UNIT=SYSDA,SPACE=(TRK,(1,1)),DISP=(,PASS) //C1 DD DSN=&&C1,UNIT=SYSDA,SPACE=(TRK,(1,1)),DISP=(,PASS) //CTL3CNTL DD OUTFIL FNAMES=(OUT 1,OUT 2,...,OUTnn), <--- code OUT 1-OUTnn // DD DSN=.C1,VOL=REF=.C1,DISP=(OLD,PASS) //OUT 1 DD DSN=... output file 1 //OUT 2 DD DSN=... output file 2 ... //OUTnn DD DSN=... output filenn <--- code OUT 1-OUTnn //TOOLIN DD Get the record count. COPY FROM(IN) USING(CTL1) Generate: SPLIT1R=x where x = count/nn. nn is the number of output files. COPY FROM(T1) TO(C1) USING(CTL2) Use SPLIT1R=x to split records contiguously among the nn output files. COPY FROM(IN) USING(CTL3) / //CTL1CNTL DD OUTFIL FNAMES=T1,REMOVECC,NODETAIL, TRAILER1=(COUNT=(M11,LENGTH=8)) / //CTL2CNTL DD OUTREC BUILD=(2X,C'SPLIT1R=', 1,8,ZD,DIV,+nn, <--- set to nn TO=ZD,LENGTH=8,8 :X) /
16
//SPLIT1R EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=Y897797.INPUT1,DISP=OLD //OUT1 DD DSN=Y897797.SPLITR1,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT2 DD DSN=Y897797.SPLITR2,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT3 DD DSN=Y897797.SPLITR3,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD SORT FIELDS=(21,5,FS,A) OUTFIL FNAMES=(OUT1,OUT2,OUT3),SPLIT1R=4 / The first four sorted records are written to the OUT1 data set, the second four sorted records are written to the OUT2 data set, the third four sorted records are written to the OUT3 data set, and the remaining two records are also written to the OUT3 data set. The resulting output data sets would contain the following records: Y897797.SPLITR1 (OUT1 DD) sorted sorted sorted sorted record record record record 1 2 3 4
Y897797.SPLITR2 (OUT2 DD) sorted sorted sorted sorted record record record record 5 6 7 8
Y897797.SPLITR3 (OUT3 DD) sorted sorted sorted sorted sorted sorted record record record record record record 9 1 11 12 13 14
Notice that the records in each output file are contiguous. Use SPLIT SPLIT is the easiest way to split an input data set into multiple output data sets if you don't need the records in each output data set to be contiguous. SPLIT can be used to split the records as evenly as possible among the output data sets. SPLIT writes one record to each output data set in rotation. Here's an example of SPLIT for an input data set with 14 records:
17
//SPLIT EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=Y897797.INPUT1,DISP=OLD //OUT1 DD DSN=Y897797.SPLIT1,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT2 DD DSN=Y897797.SPLIT2,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT3 DD DSN=Y897797.SPLIT3,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD SORT FIELDS=(21,5,FS,A) OUTFIL FNAMES=(OUT1,OUT2,OUT3),SPLIT / The first sorted record is written to the OUT1 data set, the second sorted record is written to the OUT2 data set, the third sorted record is written to the OUT3 data set, the fourth sorted record is written to the OUT1 data set, and so on in rotation. The resulting output data sets would contain the following records: Y897797.SPLIT1 (OUT1 DD) sorted sorted sorted sorted sorted record record record record record 1 4 7 1 13
Y897797.SPLIT2 (OUT2 DD) sorted sorted sorted sorted sorted record record record record record 2 5 8 11 14
Y897797.SPLIT3 (OUT3 DD) sorted sorted sorted sorted record record record record 3 6 9 12
Notice that the records in each output file are not contiguous. Use SPLITBY SPLITBY=n is another way to split the records evenly among the output data sets if you don't need the records in each output data set to be contiguous. It is similar to SPLIT, except that it writes n records to each output data set in rotation. Here's an example of SPLITBY=n for an input data set with 53 records:
18
//SPLITBY EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=Y897797.IN1,DISP=OLD //OUT1 DD DSN=Y897797.SPLITBY1,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT2 DD DSN=Y897797.SPLITBY2,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT3 DD DSN=Y897797.SPLITBY3,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD SORT FIELDS=(21,5,FS,A) OUTFIL FNAMES=(OUT1,OUT2,OUT3),SPLITBY=1 / The first ten sorted records are written to the OUT1 data set, the second ten sorted records are written to the OUT2 data set, the third ten sorted records are written to the OUT3 data set, the fourth ten sorted record are written to the OUT1 data set, and so on in rotation. The resulting output data sets would contain the following records: Y897797.SPLITBY1 (OUT1 DD) sorted records 1-1 sorted records 31-4 Y897797.SPLITBY2 (OUT2 DD) sorted records 11-2 sorted records 41-5 Y897797.SPLITBY3 (OUT3 DD) sorted records 21-3 sorted records 51-53 Notice that the records in each output file are not contiguous. Use STARTREC and ENDREC STARTREC=n and ENDREC=m can be used to select a sequential range of records to be included in each output data set. STARTREC=n starts processing at the nth record while ENDREC=m ends processing at the mth record. Here's an example of STARTREC=n and ENDREC=m:
19
//RANGE EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=Y897797.INPUT2,DISP=OLD //FRONT DD DSN=Y897797.RANGE1,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //MIDDLE DD DSN=Y897797.RANGE2,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //BACK DD DSN=Y897797.RANGE3,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD OPTION COPY OUTFIL FNAMES=FRONT,ENDREC=5 OUTFIL FNAMES=MIDDLE,STARTREC=5 1,ENDREC=22 5 OUTFIL FNAMES=BACK,STARTREC=22 6 Input record 1 through input record 500 are written to the FRONT data set. Input record 501 through input record 2205 are written to the MIDDLE data set. Input record 2206 through the last input record are written to the BACK data set. The resulting output data sets would contain the following records: Y897797.RANGE1 (FRONT DD) input record 1 input record 2 ... input record 5 Y897797.RANGE2 (MIDDLE DD) input record 5 1 input record 5 2 ... input record 22 5 Y897797.RANGE3 (BACK DD) input record 22 6 input record 22 7 ... last input record Use INCLUDE, OMIT and SAVE INCLUDE/OMIT and SAVE can be used to select specific records to be included in each output data set. The INCLUDE and OMIT operands provide all of the capabilities of the INCLUDE and OMIT statements including substring search and bit logic. SAVE can be used to select the records that are not selected for any other subset, eliminating the need to specify complex conditions. Here's an example of INCLUDE and SAVE:
20
//SUBSET EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=Y897797.INPUT3,DISP=OLD //OUT1 DD DSN=Y897797.SUBSET1,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT2 DD DSN=Y897797.SUBSET2,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //OUT3 DD DSN=Y897797.SUBSET3,DISP=(NEW,CATLG), // SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD OPTION COPY OUTFIL INCLUDE=(8,6,CH,EQ,C'ACCTNG'),FNAMES=OUT1 OUTFIL INCLUDE=(8,6,CH,EQ,C'DVPMNT'),FNAMES=OUT2 OUTFIL SAVE,FNAMES=OUT3 Records with ACCTNG in positions 8-13 are included in the OUT1 data set. Records with DVPMNT in positions 8-13 are included in the OUT2 data set. Records without ACCTNG or DVPMNT in positions 8-13 are written to the OUT3 data set. So the resulting output data sets might contain the following records: Y897797.SUBSET1 (OUT1 DD) J2 X52 ... ACCTNG ACCTNG
21
You can use DFSORT's SFF (signed free form) format to extract the sign and digits from numeric values in various forms, such as the sdddddd.dd values in this example, into ZD format and then convert the ZD values to other forms as needed. To get the sum (or total) of these sdddddd.dd values for each key, you can use SFF in an OUTFIL statement with SECTIONS and TRAILER3. Here's a DFSORT job to do that: //DECSUM EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD Sort on key SORT FIELDS=(1,3,CH,A) Use SFF to get total of extacted digits and sign from sdddddd.dd numbers for each key, and convert the totals back to sdddddd.dd numbers. OUTFIL REMOVECC,NODETAIL, SECTIONS=(1,3, TRAILER3=(1,4, copy bytes before number 5:TOT=(5,1 ,SFF,EDIT=(STTTTTT.TT),SIGNS=(,-)), total/convert 15:15,66)) copy bytes after number / Some points that need explanation: REMOVECC removes the ASA carriage control character generated when SECTIONS is used. Since we're not creating a report, we don't want the ASA carriage control character. NODETAIL eliminates the data records. We only want the trailer record generated for each key by TRAILER3. SECTIONS and TRAILER3 creates one trailer record for the data records with each key. TOT gives us the total. We use SFF to extract the sign and digits from the sdddddd.dd values into ZD values so they can be totalled. We then convert the summed ZD values back to sdddddd.dd values using EDIT and SIGNS. Alternatively, you can use a SUM statement to get the totals. But since SUM cannot use the SFF format directly, you need to use an INREC statement with SFF to convert the sdddddd.dd values into ZD values. Then you can use a SUM statement to sum the ZD values, and an OUTREC statement to convert the ZD values back to sdddddd.dd values. In this case, you can use OVERLAY in INREC and OUTREC to convert the values in place without specifying the bytes before or after the values. Here's the DFSORT job to do that: //DECSUMA EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD Use SFF to convert the sdddddd.dd numbers to ZD numbers. INREC OVERLAY=(5:5,1 ,SFF,TO=ZD,LENGTH=1 ) Sort on key SORT FIELDS=(1,3,CH,A) SUM on the ZD numbers. SUM FIELDS=(5,1 ,ZD) Use EDIT and SIGNS to convert the ZD sums back to sdddddd.dd numbers. OUTREC OVERLAY=(5:5,1 ,ZD,EDIT=(STTTTTT.TT),SIGNS=(,-)) /
22
23
24
If you wanted to delete records with 'SUI' in positions 21-23, but display the count of input records and output records in SORTOUT, you could use a DFSORT job like this: //CTINOUT EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD OPTION COPY INREC IFTHEN=(WHEN=INIT, OVERLAY=(1:C' ')), IFTHEN=(WHEN=(21,3,CH,EQ,C'SUI'), OVERLAY=(1:C' 1')) OUTFIL NODETAIL,REMOVECC, TRAILER1=('Input count: ',COUNT=(M11,LENGTH=8), ', Output count: ', TOT=(1,8,ZD,M11,LENGTH=8)) / SORTOUT would have one record that might look like this: Input count: 11, Output count: 6
Display SMF, TOD and ETOD date and time in readable form
Jorg Berning asked the following question on the IBM-MAIN mailing list: I'm trying to convert the SMF14TME field to the format hh:mm:ss. This field contains the "time since midnight, in hundredths of a second that the record was moved into the SMF buffer." Can I do this with DFSORT/ICETOOL? DFSORT provides special formats that allow you to convert SMF, TOD and ETOD values to readable format. You can use the SMF date and time formats (DT1-3 and TM1-4) in INREC, OUTREC, OUTFIL statements, and in ICETOOL's DISPLAY and OCCUR operators to display the normally unreadable SMF date and time values in a wide range of recognizable ways. For example, DT3 automatically converts the SMF date value to a Z'yyyyddd' (=C'yyyyddd) value and TM1 automatically converts the SMF time value to a Z'hhmmss' (=C'hhmmss') value. These ZD values can be used as is, or further edited using the editing features of DFSORT or ICETOOL. Likewise, you can use the TOD date and time formats (DC1-3 and TC1-4) and the ETOD date and time formats (DE1-3 and TE1-4) in INREC, OUTREC, OUTFIL, DISPLAY and OCCUR to display the normally unreadable TOD (STCK) and ETOD (STCKE) date and time values, respectively, in a wide range of recognizable ways. For example, DC1 automatically converts the TOD date value to a Z'yyyymmdd' (=C'yyyymmdd') value and TE4 automatically converts the ETOD time value to a Z'hhmmssxx' (=C'hhmmssxx') value. These ZD values can be used as is, or further edited using the editing features of DFSORT or ICETOOL. Here's an ICETOOL job that produces the report with readable SMF date and time that Jorg was looking for:
25
//SMFRPT JOB ... //STEP 1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= ICETOOL messages //DFSMSG DD SYSOUT= DFSORT messages //RAWSMF DD DSN=... SMF data //SMF# DD DSN=&&TEMPV,SPACE=(CYL,(15,15)),UNIT=SYSDA //SMF#REP DD SYSOUT= Report //TOOLIN DD ICETOOL statements COPY FROM(RAWSMF) TO(SMF#) USING(SMFI) DISPLAY FROM(SMF#) LIST(SMF#REP) TITLE('SMF Type-14 Records') DATE TIME PAGE HEADER('Time') ON(7,4,TM1,E'99:99:99') - C'hh:mm:ss' HEADER('Date') ON(11,4,DT3,E'9999-999') - C'yyyy-ddd' HEADER('Sys') ON(15,4,CH) HEADER('SMF#') ON(6,1,BI) HEADER('Jobname') ON(19,8,CH) HEADER('Datasetname') ON(69,44,CH) BLANK / //SMFICNTL DD INCLUDE COND=(6,1,BI,EQ,14) - Select SMF Type-14 records only / Here's a sample of what the report might look like: SMF TYPE-14 RECORDS TIME -------21:3 : 1 21:3 : 7 22: 7: 22: 7: 22: 7: 6 : : 5 : : 5 : :23 ... DATE -------2 5- 52 2 5- 52 2 5- 52 2 5- 52 2 5- 53 2 5- 53 2 5- 53 2 5- 53 SYS ---SMEP SMEP SMEP SMEP SMEP SMEP SMEP SMEP 2/24/ 5 SMF# ---14 14 14 14 14 14 14 14 1 :35:11 JOBNAME -------TMVSLFS TMONADMP TMVSLFS TMVSLFS TMONADMP TMONMVS TMONCICS ESS148XC - 1 -
DATASETNAME --------------------TMON.MVS3 .TMVINST TMON.SS.LMKLOAD TMON.MVS3 .TMVINST TMON.MVS3 .TMVINST TMON.SS.LMKLOAD TMON.SS.LMKASET TMON.STRATS.LMKASET SYS.EOR.CNTL
26
1RPT.FRANK LINE 1 FOR LINE 2 FOR LINE 3 FOR ... 1LINE 61 FOR LINE 62 FOR ... 1RPT.VICKY LINE 1 FOR LINE 2 FOR LINE 3 FOR LINE 4 FOR ... 1RPT.CARRIE LINE 1 FOR LINE 2 FOR ... 1RPT.HOLLY LINE 1 FOR LINE 2 FOR LINE 3 FOR ... 1RPT.MARY LINE 1 FOR LINE 2 FOR LINE 3 FOR LINE 4 FOR ... 1RPT.DAVID LINE 1 FOR LINE 2 FOR ...
FRANK REPORT FRANK REPORT FRANK REPORT FRANK REPORT FRANK REPORT
I want to extract different reports to an output file at different times. For example, one time I might want to extract the RPT.FRANK, RPT.HOLLY and RPT.MARY reports, and another time I might want to extract the RPT.CARRIE and RPT.DAVID reports. Can I do this with DFSORT? Using DFSORT's IFTHEN clauses and SPLICE operator, we can add group numbers to the records, and propagate the report id (RPT.cccccc) to each record of the group. Then we can use an INCLUDE condition specifying the report ids for the groups we want to keep, or an OMIT condition specifying the report ids for the groups we want to delete. We make a copy of the 10-byte report id ('RPT.cccccc') at the end of the record in positions 134-143. We follow that by the 8-byte group number (00000001 for the first group, 00000002 for the second group,and so on) in positions 144-151 and the 8-byte temp. number in positions 152-159. The temp. number is just used to get the group number. Here's the DFSORT job that does the trick. //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... input file //T1 DD DSN=&&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... output file //TOOLIN DD
27
Reformat records by group as follows - the tempnum is just used to get the group number ( 1, 2, etc). RPT.rptida |RPT.rptida| 1|blanks | detail |blanks....| 1|tempnum| detail |blanks....| 1|tempnum| ... RPT.rptidb |RPT.rptidb| 2|blanks | detail |blanks....| 2|tempnum| detail |blanks... | 2|tempnum| ... COPY FROM(IN) TO(T1) USING(CTL1) Use the group number to splice 'RPT.cccccc' from each group header record into the detail records for that group as follows: RPT.rptida |RPT.rptida| 1|blanks | detail |RPT.rptida| 1|tempnum| detail |RPT.rptida| 1|tempnum| ... RPT.rptidb |RPT.rptidb| 2|blanks | detail |RPT.rptidb| 2|tempnum| detail |RPT.rptidb| 2|tempnum| ... Then do an INCLUDE using RPT.rptida for the groups you want. SPLICE FROM(T1) TO(OUT) ON(144,8,ZD) WITHALL WITH(1,133) KEEPBASE USING(CTL2) / //CTL1CNTL DD INREC IFTHEN=(WHEN=INIT,OVERLAY=(144:SEQNUM,8,ZD)), IFTHEN=(WHEN=(2,4,CH,EQ,C'RPT.'), OVERLAY=(134:2,1 ,144:SEQNUM,8,ZD)), IFTHEN=(WHEN=NONE, OVERLAY=(152:SEQNUM,8,ZD, 144:144,8,ZD,SUB,152,8,ZD,M11,LENGTH=8)) / //CTL2CNTL DD OUTFIL FNAMES=OUT, Use an INCLUDE with 134,7,CH,EQ,C'RPT.cccccc' for the groups you want. INCLUDE=(134,1 ,CH,EQ,C'RPT.FRANK',OR, 134,1 ,CH,EQ,C'RPT.HOLLY',OR, 134,1 ,CH,EQ,C'RPT.MARY'), Remove the copy of the report id, the group number and the temp. number. BUILD=(1,133) / The INCLUDE condition selects groups RPT.FRANK, RPT.HOLLY and RPT.MARY, so OUT looks like this:
28
1RPT.FRANK LINE 1 FOR LINE 2 FOR LINE 3 FOR ... 1LINE 61 FOR LINE 62 FOR ... 1RPT.HOLLY LINE 1 FOR LINE 2 FOR LINE 3 FOR ... 1RPT.MARY LINE 1 FOR LINE 2 FOR LINE 3 FOR LINE 4 FOR ...
FRANK REPORT FRANK REPORT FRANK REPORT FRANK REPORT FRANK REPORT
The header record is identified by 'HDR' and has the date in 'yyyy/mm/dd' format. The trailer record is identified by 'TRL'. All the other records are detail records with an amount in dddd.dds format (d is 0-9 and s is + or -).
29
I want to sort the groups by the date (ascending) in the header records, and also sort the detail records within each group by the amount (descending). If multiple groups have the same date (like the 2005/04/09 groups above), I want to keep their relative order. So my output should look like this: HDR 1 3 2 TRL HDR 3 1 2 TRL HDR 2 3 1 4 5 TRL HDR 2 3 1 4 TRL 2 5/ 3/11 123.86+ 61.16+ 98. 7T 31893 2 5/ 4/ 9 1496.28+ 282.15+ 8. T2 1576 2 5/ 4/ 9 23 8. + 96.72+ 1 6.272 6.992 8.82T2 1573 2 5/ 4/1 5213.75+ 861.51+ 23. 5753.9 T862143
Can I use DFSORT or ICETOOL to do this? Using DFSORT's IFTHEN clauses and SPLICE operator, we can add group numbers to the records, propagate the date to each record of the group and add a "code" of 'A' for header records, 'B' for detail records and 'C' for trailer records. Then we can sort by the date and group number to get the groups in the right order, and sort by "code" and amount to keep the header and trailer records for each group in the right place while rearranging the amounts within each group. We make a copy of the 10-byte date ('yyyy/mm/dd') at the end of the record in positions 31-40. We follow that with the "code" ('A', 'B' or 'C') in position 41, the 8-byte group number (00000001 for the first group, 00000002 for the second group,and so on) in positions 42-49, and the 8-byte temp. number in positions 50-57. The temp. number is just used to get the group number. We use DFSORT's signed free form (SFF) format to sort the dddd.dds values. Here's the DFSORT job that does the trick. //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... input file //T1 DD DSN=&&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //T2 DD DSN=&&T2,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... output file //TOOLIN DD Reformat records by group as follows - the tempnum is just used to get the group number ( 1, 2, etc). HDR yyyy/mm/dd |yyyy/mm/dd|A|groupnum|tempnum| xxx dddd.dds | |B|groupnum|tempnum| xxx dddd.dds | |B|groupnum|tempnum|
30
... TRL ... | |C|groupnum|tempnum| COPY FROM(IN) TO(T1) USING(CTL1) Use the group number to splice 'yyyy/mm/dd' from each group header record into the detail and trailer records for that group as follows: HDR yyyy/mm/dd |yyyy/mm/dd|A|groupnum|tempnum| xxx dddd.dds |yyyy/mm/dd|B|groupnum|tempnum| xxx dddd.dds |yyyy/mm/dd|B|groupnum|tempnum| ... TRL ... |yyyy/mm/dd|C|groupnum|tempnum| SPLICE FROM(T1) TO(T2) ON(42,8,ZD) WITHALL WITH(1,3 ) WITH(41,1) KEEPBASE Sort on the following fields: - yyyy/mm/dd date ascending (to get groups in date order) - group number ascending (to handle groups with same date) - code byte ascending (to get header, detail and trailer in right order) - amount descending. (to get amount within groups in order). Use SFF format to handle the decimal point and trailing sign in the amount. SORT FROM(T2) TO(OUT) USING(CTL2) / //CTL1CNTL DD INREC IFTHEN=(WHEN=INIT,OVERLAY=(42:SEQNUM,8,ZD)), Set up the header records with date, code 'A' and groupnum. IFTHEN=(WHEN=(1,3,CH,EQ,C'HDR'), OVERLAY=(31:6,1 ,41:C'A',42:SEQNUM,8,ZD)), Set up the detail and trailer records with date, code 'B' and groupnum. IFTHEN=(WHEN=(1,3,CH,NE,C'HDR'), OVERLAY=(41:C'B',5 :SEQNUM,8,ZD, 42:42,8,ZD,SUB,5 ,8,ZD,M11,LENGTH=8), HIT=NEXT), Overlay the code of 'B' with 'C' for the trailer records. IFTHEN=(WHEN=(1,3,CH,EQ,C'TRL'), OVERLAY=(41:C'C')) / //CTL2CNTL DD Sort by date, group number, code and amount. SORT FIELDS=(31,1 ,CH,A, - date 42,8,ZD,A, - group number 41,1,CH,A, - code 6,9,SFF,D) - amount Remove date, code, group number and temp. number. OUTREC FIELDS=(1,3 ) /
Set RC=12 or RC=4 if file is empty, has more than n records, etc
The following related questions have been asked by various customers: I need to check if a data set is empty or not. If it's empty I want to skip all other steps. Is there any way I can check for empty data sets?
31
I would like to skip certain steps in my job if a file is empty. This would be easiest if a utility would generate a non-zero RC if the input file is empty. I have several datasets that I need to sort together. How can I terminate if the total count of records in these data sets is greater than 5000. I have a file that always has a header and trailer record and may or may not have data records. Is there any way to check if there are no data records in the file? ICETOOL can easily satisfy all of these requests. You can use ICETOOL's COUNT operator to set a return code of 12 or 4 if a specified data set is EMPTY, NOTEMPTY, HIGHER(n), LOWER(n), EQUAL(n) or NOTEQUAL(n), where n is a specified number of records (for example, 5000). This makes it easy to control the execution of downstream operators or steps using JCL facilities like IF or COND. If you use the RC4 operand, ICETOOL sets RC=4; otherwise it sets RC=12. For example, in the following ICETOOL job, the EMPTY operand of COUNT is used to stop STEP2 from being executed if the IN data set is empty. ICETOOL sets RC=12 if the IN data set is empty, or RC=0 if the IN data set is not empty. ICETOOL only reads one record to determine if the data set is empty or not empty, regardless of how many records there are in the data set. //STEP1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... //TOOLIN DD SET RC=12 IF THE 'IN' DATA SET IS EMPTY, OR SET RC= IF THE 'IN' DATA SET IS NOT EMPTY COUNT FROM(IN) EMPTY / // IF STEP1.RC = THEN // STEP2 WILL RUN IF 'IN' IS NOT EMPTY // STEP2 WILL NOT RUN IF 'IN' IS EMPTY //STEP2 EXEC ... ... // ENDIF In this next example, the HIGHER(5000) operand of COUNT is used to skip a SORT operation if the count of records in three data sets is greater than 5000. ICETOOL sets RC=12 if the CONCAT data sets have a total record count greater than 5000, or a RC=0 if the total record count is less than or equal to 5000. MODE STOP (the default) tells ICETOOL not to execute the SORT operation if the COUNT operation sets RC=12. //SRT1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //CONCAT DD DSN=... // DD DSN=... // DD DSN=... //OUT DD DSN=... //TOOLIN DD SORT THE 'CONCAT' DATA SETS ONLY IF THEY HAVE LE 5 RECORDS MODE STOP COUNT FROM(CONCAT) HIGHER(5 ) SORT FROM(CONCAT) TO(OUT) USING(CTL1) / //CTL1CNTL DD SORT FIELDS=(25,8,BI,A) /
32
In this last example, the EMPTY operand of COUNT is used to stop S2 from being executed if the INPUT data set doesn't have any data records between the header and trailer. An OMIT statement is used with COUNT to delete the header and trailer record, leaving only the data records as a subset of the INPUT data set. ICETOOL sets RC=4 (because the RC4 operand is specified) if the subset of records is empty, or RC=0 if the subset of records is not empty. //S2 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //INPUT DD DSN=... //TOOLIN DD SET RC=4 IF 'INPUT' ONLY HAS A HEADER AND TRAILER, OR SET RC= IF 'INPUT' HAS DATA RECORDS. COUNT FROM(INPUT) EMPTY USING(HDTL) RC4 / //HDTLCNTL DD OMIT COND=(1,6,CH,EQ,C'HEADER',OR,1,7,CH,EQ,C'TRAILER') / // IF S1.RC = THEN // S2 WILL RUN IF 'IN' IS NOT EMPTY // S2 WILL NOT RUN IF 'IN' IS EMPTY //S2 EXEC ... ... // ENDIF
so we need to squeeze out the trailing blanks like this: DELETE 'dsn(ABC)' Fortunately, DFSORT's SQZ function can do that quite easily. Here's a job that will do the trick. For the example, we're using USERID.TEST.PDS as the name of the PDS. Be sure to replace USERID.TEST.PDS in the LISTDS and Symbol statement below with the actual name of your PDS.
33
//DELMEMS JOB ... // Generate LISTDS output //STEP1 EXEC PGM=IKJEFT 1 //SYSTSPRT DD DSN=&&MBRS,DISP=(,PASS),SPACE=(TRK,(5,5)),UNIT=SYSDA, // RECFM=FB,LRECL=8 //SYSPRINT DD SYSOUT= //SYSIN DD DUMMY //SYSTSIN DD LISTDS 'USERID.TEST.PDS' MEMBERS / // DFSORT step to remove unwanted records from LISTDS output // and create DELETE statements acceptable to IDCAMS // (that is, with no trailing blanks) //SHOWIT EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SYMNAMES DD name,'USERID.TEST.PDS' / //SORTIN DD DSN=&&MBRS,DISP=(OLD,PASS) //SORTOUT DD DSN=&&C1,UNIT=SYSDA,SPACE=(TRK,(5,5)),DISP=(,PASS) //SYSIN DD OPTION SKIPREC=9 OMIT COND=(1,1,CH,NE,C' ') SORT FIELDS=COPY OUTREC IFOUTLEN=8 , IFTHEN=(WHEN=INIT, BUILD=(C' DELETE ''',name,C'(',3,8,C')''')), IFTHEN=(WHEN=INIT, OVERLAY=(9:9,72,SQZ=(SHIFT=LEFT))) / // IDCAMS step to process DELETE statements generated by DFSORT //STEP3 EXEC PGM=IDCAMS //SYSPRINT DD SYSOUT= //SYSIN DD DSN=&&C1,DISP=(OLD,PASS) Some points that need explanation: We're using name as a DFSORT Symbol for the actual name of the PDS. This makes it easy to change the name without having to retype it in each BUILD operand. SKIPREC gets rid of the unwanted LISTDS header records. OMIT gets rid of the unwanted LISTDS trailer records. The two IFTHEN clauses handle building the DELETE statement for each member. The first IFTHEN clause builds statements of the form: DELETE 'name(mbr )'
where mbr is 8 characters padded on the right with blanks. The second IFTHEN clause uses DFSORT's SQZ feature to squeeze out any blanks between the member name and the ')'. This creates the appropriate DELETE statement for each member name length (1 to 8 characters) without trailing blanks: DELETE 'name(mbr)' Note that this trick deletes only member names, not ALIAS names. It's a good idea to compress the PDS after you delete the members.
34
For PDSs with lots of members, you can improve the performance of the IDCAMS step by deleting the members in reverse order. To do this, just change: SORT FIELDS=COPY in the SHOWIT step to: SORT FIELDS=(3,8,CH,D) to sort the members in descending order.
OUT would contain the following records: A52 RECORD 1 J 3 RECORD 1 M72 RECORD 1 and XSORTSUM would contain the following records:
Smart DFSORT Tricks
35
J 3 RECORD 2 M72 RECORD 2 M72 RECORD 3 But SELECT can do much more than that. Besides FIRST, it also lets you use ALLDUPS, NODUPS, HIGHER(x), LOWER(y), EQUAL(v), LAST, FIRSTDUP and LASTDUP. And you can use TO(outdd) alone, DISCARD(savedd) alone, or TO(outdd) and DISCARD(savedd) together, for any of these operands. So you can create data sets with just the selected records, just non-selected records, or with both the selected records and nonselected records, for all of these cases. Here's a few more SELECT statements to show some of its capabilities: Put duplicates in DUPS and non-duplicates in NODUPS SELECT FROM(DATA) TO(DUPS) ON(5,8,CH) ALLDUPS DISCARD(NODUPS) Put records with 5 occurrences (of the key) in EQ5 SELECT FROM(DATA) TO(EQ5) ON(5,8,CH) EQUAL(5) Put records with more than 3 occurrences (of the key) in GT3, and records with 3 or less occurrences in LE3. SELECT FROM(DATA) TO(GT3) ON(5,8,CH) HIGHER(3) DISCARD(LE3) Put records with 9 or more occurrences in OUT2. SELECT FROM(DATA) ON(5,8,CH) LOWER(9) DISCARD(OUT2) Put last of each set of duplicates in DUP1 SELECT FROM(DATA) TO(DUP1) ON(5,8,CH) LASTDUP
36
'Packed-Dec' to PD 'Binary' with S9 to FI 'Binary' without S9 to BI 'Comp-1' to FL 'Comp-2' to FL Anything else to CH cobdfsym converts COBOL 88 values to DFSORT symbols as follows: 'literal' to 'literal' (for example, 'APPROVED'). When a COBOL statement sets more than one literal, cobdfsym generates a DFSORT Symbols statement that sets the symbol to the first literal. decimal number to decimal number (for example, 14). When a COBOL statement sets more than one decimal number, cobdfsym generates a DFSORT Symbols statement that sets the symbol to the first decimal number. SPACES to ' ' ( blank). ZERO to 0 (decimal zero). LOW-VALUE to X'00' (binary zero). If any of the translated symbols do not meet your needs, you can fine-tune them by editing the DFSORT Symbols data set produced by cobdfsym. Here's an example of a job to do the COBOL compile and REXX processing. //SYSLIB must point to the library that contains your COBOL copybook. //SYSPROC must point to the library in which you've stored cobdfsym. //SYMNAMES must point to the output data set or member for the DFSORT Symbols data set to be produced by cobdfsym; this data set must have, or will be given, RECFM=FB and LRECL=80. Be sure to specify MAP in the PARM field.
37
// // GENERATE SYMBOLIC NAMES FOR DFSORT USING COBOL COPYBOOK // //COBCOMP EXEC PGM=IGYCRCTL, // PARM=('APOST,RENT,NOSEQ,MAP', // 'BUF(2 K),OPT(STD),TERM,LIB') //SYSLIB DD DSN=CLIB,DISP=SHR -- library with COBOL copybook //SYSPRINT DD DSN=&&COBLIST,DISP=(,PASS),UNIT=SYSDA, // SPACE=(TRK,(1 ,1 )) //SYSTERM DD SYSOUT= //SYSIN DD IDENTIFICATION DIVISION. PROGRAM-ID. DUMMYPGM. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. COPY DCLSYM 1. -- COBOL COPY statement PROCEDURE DIVISION. GOBACK. //SYSLIN DD DUMMY //SYSUT1 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT2 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT3 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT4 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT5 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT6 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA //SYSUT7 DD SPACE=(CYL,(1 ),,,ROUND),UNIT=SYSDA // // INTERPRET COBOL LISTING AND CREATE DFSORT SYMNAMES DATA SET // //SYMNAMES EXEC PGM=IKJEFT1A, // COND=( 4,LT), // PARM='%COBDFSYM' //SYSPROC DD DSN=RLIB,DISP=SHR -- library with cobdfsym REXX program //SYSTSPRT DD SYSOUT= //SYSTSIN DD DUMMY //COBLIST DD DSN=&&COBLIST,DISP=(OLD,PASS) //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM 1),DISP=SHR -- DFSORT symbols As an example, if the DCLSYM01 copybook looked like this: 1 PACKAGE-RECORD. 5 PACKAGE-HEADER. 1 PACKAGE-HEADER-1 1 FILLER 1 PACKAGE-HEADER-2 1 FILLER 1 PACKAGE-SEQUENCE 5 CUSTOMER-GROUP. 1 CG-NAME 1 CG-COUNT 1 CG-DATE 1 CG-TIME 1 CG-TYPE 1 CG-LIMIT 1 CG-STATUS 88 APPROVED 88 DENIED
PIC X(3 ). PIC 9(1 ) COMP-3. PIC 9( 6) COMP. PIC 9( 8) COMP. PIC S9( 2) COMP. PIC S9( 7). PIC X( 8). VALUE 'APPROVED'. VALUE 'DENIED '.
38
VALUE SPACES. PIC 99. VALUE 14. VALUE 24. VALUE ZERO.
the DFSORT Symbols created in DFSORT.SYMBOLS(DCLSYM01) would look like this: PACKAGE-RECORD,1,84,CH PACKAGE-HEADER,1,21,CH PACKAGE-HEADER-1,1,13,CH PACKAGE-HEADER-2,15,1,CH PACKAGE-SEQUENCE,17,5,PD CUSTOMER-GROUP,22,63,CH CG-NAME,22,3 ,CH CG-COUNT,52,6,PD CG-DATE,58,4,BI CG-TIME,62,4,BI CG-TYPE,66,2,FI CG-LIMIT,68,7,ZD CG-STATUS,75,8,CH APPROVED,'APPROVED' DENIED,'DENIED ' PENDING,' ' CG-COUNTY-NO,83,2,ZD DUTCHESS,14 KINGS,24 NOCOUNTY, You could use these symbols in a DFSORT job by specifying: //S1 EXEC PGM=ICEMAN //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM 1),DISP=SHR ... You could use these symbols in an ICETOOL job by specifying: //S2 EXEC PGM=ICETOOL //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM 1),DISP=SHR ... Here's the cobdfsym REXX program: /REXX - COBDFSYM : Create DFSORT symbols from COBOL listing Freeware courtesy of SEB IT Partner and IBM trace r / call read_coblist call fix_duplicates call put_symnames exit Put_symnames: / Write generated symbol definitions / do i = 1 to nf queue dnam.i','dval.i say dnam.i','dval.i end / Write appended symbol definitions / do i = 1 to na
Smart DFSORT Tricks
39
queue dapp.i say dapp.i end queue '' 'EXECIO DISKW SYMNAMES (FINIS' return Put_line: / Analyze Data Division Map line / parse var line linenr level dataname . parse var dataname dataname '.' . if dataname = 'FILLER' then Return if level = 'PROGRAM-ID' then Return if level = 88 then Do nf = nf + 1 dnam.nf = dataname dval.nf = d88.linenr dlvl.nf = lev Return end blk = substr(line,64,4) if level = 1 then nf = hexoff = substr(line,79,3) || substr(line,83,3) if hexoff = ' ' then hexoff = ' ' parse var line 92 asmdef datatyp . if datatyp = 'Group' | datatyp = 'Grp-VarLen' then parse var asmdef . 'CL' len else do len = left(asmdef,length(asmdef)-1) if right(asmdef,2) = '1H' then len = 2 if right(asmdef,2) = '1F' then len = 4 if right(asmdef,2) = '2F' then len = 8 end select when datatyp = 'Group' then typ when datatyp = 'Grp-VarLen' then typ when datatyp = 'Display' then typ when datatyp = 'Disp-Num' then typ when datatyp = 'Packed-Dec' then typ when datatyp = 'Binary' then typ when datatyp = 'Comp-1' then typ when datatyp = 'Comp-2' then typ otherwise typ end if typ = 'FI' then do if s9.linenr /= 'Y' then typ = 'BI' end else do if typ = 'ZD' then if sp.linenr = 'Y' then if ld.linenr = 'Y' then typ = 'FS' else typ = 'CST' else if ld.linenr = 'Y' then typ = 'CLO' end off = 1 + x2d(hexoff) nf = nf + 1 dnam.nf = dataname
= = = = = = = = =
40
dval.nf = off','len','typ dlvl.nf = lev Return Read_COBLIST: l88 = lx = na = 'EXECIO DISKR COBLIST (FINIS' parse pull line do until substr(line,2,16) = ' LineID PL SL ' parse pull line end / Process program text lines / do until substr(line,2,16) /= ' LineID PL SL ' parse pull line do until left(line,1) = '1' call Check_Code_line parse pull line end parse pull line end / Skip lines / do until substr(line,2,18) = 'LineID Data Name' parse pull line end / Process Data Division Map lines / do until substr(line,2,18) /= 'LineID Data Name' parse pull line do until left(line,1) = '1' call Put_line parse pull line end parse pull line parse pull line end / Skip rest / do until queued() = parse pull line end Return Fix_Duplicates: / Append _n to any duplicate data names / nd = tdup. = '' Do i = 1 to nf nam = dnam.i parse var tdup.nam flag i1 if flag = '' then do tdup.nam = ' ' i iterate end if flag = ' ' then do nd = nd + 1 td1.nd = i1 i tdup.nam = '1' nd iterate end td1.i1 = td1.i1 i
Smart DFSORT Tricks
41
End Do id = 1 to nd parse var td1.id i tail n = Do while i /= '' n = n + 1 dnam.i = dnam.i || '_' || n parse var tail i tail End End Return Check_code_line: / Analyze program text line , capture 88 VALUE clauses / / Capture S9, LEADING, SEPARATE parameters / / Make append lines from + comments / parse var line 4 linenr 1 flag . 19 . 25 stmt 91 if linenr = '' then return linenr = linenr + if left(stmt,2) = '+' then do na = na + 1 dapp.na = substr(stmt,3) return end if left(stmt,1) = '' then return if left(stmt,1) = '/' then return if lastpos('.',stmt) = then do parse pull line if left(line,1) = '1' then parse pull line if substr(line,2,16) = ' LineID PL SL ' then parse pull line parse var line 4 x1 1 x2 . 19 . 25 stmt2 91 stmt = stmt||stmt2 end parse var stmt w1 . if w1 = '88' then do l88 = linenr if l88 /= then do parse var stmt . 'VALUE' tail if tail /= then do parse var tail value '.' . d88.l88 = strip(value) if left(d88.l88,6) = 'SPACES' then d88.l88 = ''' ''' if left(d88.l88,4) = 'ZERO' then d88.l88 = ' ' if left(d88.l88,9) = 'LOW-VALUE' then d88.l88 = 'X'' ''' l88 = end end return end else do lx = linenr if lx /= then do parse var stmt x1 x2 x3 if pos(' S9',x3) /= then s9.lx = 'Y' if pos(' LEADING',x3) /= then ld.lx ='Y' if pos(' SEPARATE',x3) /= then sp.lx = 'Y'
42
and File B has the following records: 1 3 4 5 BBBBB DDDDD FFFFF HHHHH
I want to join the data fields for pairs of records with the same key to get the following output: 1 AAAAA BBBBB 3 EEEEE DDDDD 4 GGGGG FFFFF Can I do that using DFSORT/ICETOOL? Below is an ICETOOL job that can do it. The trick is to reformat the fields of the IN1 and IN2 files so you can join them with the SPLICE operator. SPLICE helps you perform various file join and match operations. Note: For this example, the key and data fields are in the same locations in both files. But this same technique can be used if the key and/or data fields are in different locations in the files by reformatting the two files appropriately.
43
//DFSORT EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN1 DD DSN=... file 1 //IN2 DD DSN=... file 2 //TMP1 DD DSN=&&TEMP1,DISP=(MOD,PASS),SPACE=(TRK,(5,5)),UNIT=SYSDA //OUT DD DSN=... output file //TOOLIN DD For this example, the fields (p,m) are as follows: IN1: sort key - 1,3 f1fld - 5,5 IN2: sort key - 1,3 f2fld - 5,5 Reformat the IN1 data set so it can be spliced COPY FROM(IN1) TO(TMP1) USING(CPY1) Reformat the IN2 data set so it can be spliced COPY FROM(IN2) TO(TMP1) USING(CPY2) Splice records with matching IN1/IN2 keys SPLICE FROM(TMP1) TO(OUT) ON(1,3,CH) WITH(11,5) / //CPY1CNTL DD Use OUTREC to create |key |f1fld |blank| OUTREC FIELDS=(1:1,3,5:5,5,11:5X) / //CPY2CNTL DD Use OUTREC to create:|key |blank |f2fld| OUTREC FIELDS=(1:1,3,11:5,5) /
44
Normally, we're asked how to join files on a key, but in this case there is no key; we just want to join the files record-by-record. Well, no problem, we'll just create a key we can use by adding a sequence number (1, 2, ...) to the records in file1 and a sequence number (1, 2, to the records in file2. Then we can join the fields from the 1 records, from the 2 records, and so on using the same technique we used for joining files on a key. Below is an ICETOOL job that can do this join by using the SPLICE operator. SPLICE helps you perform various file join and match operations. //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN1 DD DSN=... file1 //IN2 DD DSN=... file2 //TMP1 DD DSN=&&TEMP1,DISP=(MOD,PASS),SPACE=(TRK,(5,5)),UNIT=SYSDA //OUT DD DSN=... output file //TOOLIN DD Reformat the IN1 data set so it can be spliced COPY FROM(IN1) TO(TMP1) USING(CTL1) Reformat the IN2 data set so it can be spliced COPY FROM(IN2) TO(TMP1) USING(CTL2) Splice records with matching sequence numbers. SPLICE FROM(TMP1) TO(OUT) ON(11,8,PD) WITH(6,5) USING(CTL3) / //CTL1CNTL DD Use OUTREC to create: |f1fld|blank|seqnum| OUTREC FIELDS=(1:1,5,11:SEQNUM,8,PD) / //CTL2CNTL DD Use OUTREC to create: |blank|f2fld|seqnum| OUTREC FIELDS=(6:1,5,11:SEQNUM,8,PD) / //CTL3CNTL DD Use OUTFIL OUTREC to remove the sequence number OUTFIL FNAMES=OUT,OUTREC=(1,1 ) /
VB to FB conversion
DFSORT makes it easy to do VB to FB conversion and FB to VB conversion.. OUTFIL adds lots of tricks to your DFSORT toolkit, including the ability to convert a variable-length data set to a fixed-length data set while sorting, copying or merging. With the VLFILL=byte option of OUTFIL, you can even do the conversion easily when "short" records are present. The VTOF or CONVERT and OUTREC operands of OUTFIL can be used to change variable-length (e.g. VB) input records to fixed-length (e.g. FB) output records. VTOF or CONVERT indicates that conversion is to be performed and OUTREC defines the reformatted records. All output data sets for which VTOF or CONVERT is used must have or will be given fixed-length record formats. Here's an example of OUTFIL conversion: SORT FIELDS=(7,8,CH,A) OUTFIL FNAMES=FB1,VTOF,OUTREC=(5,76)
45
The FB output records for the FB1 data set will be 76 byte records containing positions 5-80 of the VB input records. Only positions 5-80 of VB input records longer than 80 bytes will be used for the 76-byte FB output records. But what about VB input records that are "shorter" than the 80 bytes needed to copy input positions 5-80 to the 76-byte FB output records? No problem. DFSORT automatically uses the VLFILL=C' ' option with VTOF or CONVERT to replace missing bytes in "short" OUTFIL OUTREC fields with blanks. So all of your short VB input records will be padded with blanks to create 76-byte FB output records. If you want to select your own padding byte, just specify the VLFILL=byte option. For example, here's how you'd use an asterisk as the padding byte for the previous example: SORT FIELDS=(7,8,CH,A) OUTFIL FNAMES=FB1,VTOF,OUTREC=(5,76),VLFILL=C''
FB to VB conversion
DFSORT makes it easy to do FB to VB conversion and VB to FB conversion. The FTOV operand of OUTFIL can be used to change fixed-length (e.g. FB) input records to variable-length (e.g. VB) output records. If FTOV is specified without OUTREC, the entire FB input record is used to build the VB output record. If FTOV is specified with OUTREC, the specified fields from the FB input record are used to build the VB output record. The VB output records will consist of a 4-byte RDW followed by the FB data. All output data sets for which FTOV is used must have or will be given variable-length record formats. Here's an example of FB to VB conversion: OUTFIL FNAMES=FBVB1,FTOV OUTFIL FNAMES=FBVB2,FTOV,OUTREC=(1,1 ,C'=',21,1 ) The VB output records for the FBVB1 data set will contain a 4-byte RDW followed by the FB input record. The VB output records for the FBVB2 data set will contain a 4-byte RDW followed by the characters from input positions 1-10, an '=' character, and the characters from input positions 21-30. All of the VB output records created with FTOV will consist of: RDW + input fields Since all of the input fields from the FB input records are the same, all of the VB output records will be the same length. But if you have trailing characters such as blanks, asterisks, binary zeros, etc, you can create true VB output records with different lengths by using the VLTRIM=byte option of OUTFIL. VLTRIM=byte can be used with FTOV to remove trailing bytes of the specified type from the end of the VB output records. Here are some examples: OUTFIL FNAMES=FBVB3,FTOV,VLTRIM=C'' OUTFIL FNAMES=FBVB4,FTOV,VLTRIM=X'4 ' OUTFIL FNAMES=FBVB5,FTOV,VLTRIM=X' ' FBVB3 will contain VB output records without trailing asterisks. FBVB4 will contain VB output records without trailing blanks. FBVB5 will contain VB output records without trailing binary zeros.
46
To further illustrate how FTOV and VLTRIM=byte work, say you have the following 17-byte FB data records that you want to convert to VB data records: 123456 3 ABCDEFGHIJ22 If you use: OUTFIL FTOV the following VB output records will be written (4-byte RDW followed by data): Length | Data 21 123456 21 3 21 ABCDEFGHIJ22 21 but if you use: OUTFIL FTOV,VLTRIM=C'' the following VB output records will be written (4-byte RDW followed by data): Length | Data 1 123456 8 3 21 ABCDEFGHIJ22 5
I need the data records to be sorted on positions 14-23. If I do a normal sort, the header and footer records end up out of place in the output file. How can I keep the header as the first record and the trailer as the last record, but still sort the data records in between? In the data shown for this question, the header record is identified by the string 'HEDR' and the trailer record is identified by the string 'TRAL'. We can use those identifiers to keep the header and trailer records in their places as we sort the data records. Here's a DFSORT job that shows how:
47
//S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD HEDRINF1 1D342 5 413 1 34 1 19161 22 5 3 11989 717FANDERSON 34 213284 22 5 31 1985 7 5FLYONS 34 C18176 22 5 3 119 1 1FMITCHELL 34 D196589 52 5 3 12 312 3MBENTON 34 B252918 62 5 3 11994 122MGONZALEZ 34 4266757 1 2 5 3 119 1 1MMATHEW 34 B2868 7 52 5 33 19951 5MSOLORIO 34 C354835 72 5 3 11966 93 FMACARIO 34 F4 7832 32 5 3222 5 318FWILSON TRALINF1 1D342 5 413 1 294138 / //SORTOUT DD DSN=... output file //SYSIN DD Put special key of '1' in 81 for data records. INREC IFTHEN=(WHEN=INIT,OVERLAY=(81:C'1')), For header record, put special key of ' ' in 81. IFTHEN=(WHEN=(1,4,CH,EQ,C'HEDR'),OVERLAY=(81:C' ')), For trailer record, put special key of '9' in 81. IFTHEN=(WHEN=(1,4,CH,EQ,C'TRAL'),OVERLAY=(81:C'9')) Sort by special key (' ', '1' or '9') and then regular key. SORT FIELDS=(81,1,CH,A,14,1 ,CH,A) Remove special key. OUTREC FIELDS=(1,8 ) / By using a special key of '0' for the header record, '1' for the data records and '9' for the trailer records before the regular key, we ensure that the header is first and the trailer is last. The '0' special key is unique and will be sorted first so it's regular key doesn't matter. Likewise, the '9' special key for the trailer is unique and will be sorted last so it's regular key doesn't matter. However, since all of the data records have the same special key of '1', they will be sorted between the header and trailer, and the regular key will determine their order. The output records will look as follows: HEDRINF1 1D342 5 413 34 B252918 62 34 B2868 7 52 34 C18176 22 34 C354835 72 34 D196589 52 34 F4 7832 32 34 1 19161 22 34 213284 22 34 4266757 1 2 TRALINF1 1D342 5 413 1 5 5 5 5 5 5 5 5 5 3 11994 122MGONZALEZ 33 19951 5MSOLORIO 3 119 1 1FMITCHELL 3 11966 93 FMACARIO 3 12 312 3MBENTON 3222 5 318FWILSON 3 11989 717FANDERSON 31 1985 7 5FLYONS 3 119 1 1MMATHEW 1 294138
48
or: OPTION COPY OUTFIL ENDREC=1 But keeping the last n records of an input data set is a bit more challenging since DFSORT doesn't have any built-in functions to do that. The trick is to attach a sequence number to the records and sort them in descending order by the sequence number. That way, the last records end up at the front of the file and you can use STOPAFT or ENDREC to select the last n records. Unfortunately, those last n records will be in reverse order, so you have to sort them again in ascending order by the sequence number to get them back in their original order. It's less complicated then it sounds as illustrated by this DFSORT/ICETOOL job to get the last 10 records for an input data set with RECFM=FB and LRECL=80: //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... FB input file //TEMP DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... FB output data set //TOOLIN DD SORT FROM(IN) USING(CTL1) SORT FROM(TEMP) TO(OUT) USING(CTL2) / //CTL1CNTL DD Add sequence numbers. Use them to reverse the order of the records. INREC FIELDS=(1,8 ,SEQNUM,8,BI) SORT FIELDS=(81,8,BI,D) Get the last 1 records OUTFIL FNAMES=TEMP,ENDREC=1 / //CTL2CNTL DD Use the sequence numbers to put the last 1 records back in their original order. SORT FIELDS=(81,8,BI,A) Remove the sequence numbers. OUTREC FIELDS=(1,8 ) / Here's another example. This one gets the last 525 records from a VB input data set:
49
//S2 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... VB input file //TEMP DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... VB output data set //TOOLIN DD SORT FROM(IN) USING(CTL1) SORT FROM(TEMP) TO(OUT) USING(CTL2) / //CTL1CNTL DD Add sequence numbers. Use them to reverse the order of the records. INREC FIELDS=(1,4,5:SEQNUM,8,BI,13:5) SORT FIELDS=(5,8,BI,D) Get the last 525 records OUTFIL FNAMES=TEMP,ENDREC=525 / //CTL2CNTL DD Use the sequence numbers to put the last 525 records back in their original order. SORT FIELDS=(5,8,BI,A) Remove the sequence numbers. OUTREC FIELDS=(1,4,13) /
Sample records
This question was posed on the MVSHELP Help board: I would like to create a subset of a rather large file containing 66 million rows. Not knowing how the data in the file is ordered, I'd like to just make a subset of the file by selecting every 100th record. Does anyone know of a utility that performs this function? Below is an ICETOOL job that can sample every 100th record using the SAMPLE=n parameter of OUTFIL. SAMPLE=n and SAMPLE=(n,m) let you sample records in various ways. In this case STARTREC=100 is used to write record 100 to SORTOUT, and SAMPLE=100 is used to write records 200, 300, and so on to SORTOUT. //S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD OPTION COPY OUTFIL STARTREC=1 ,SAMPLE=1 /
50
Can you tell me how to replace all the occurrences of one character (say 'a') with another character (say 'b') in a sequential file. Translation features of INREC, OUTREC and OUTFIL OUTREC make it easy to satisfy all of these requests. The TRAN=ALTSEQ operand can be used to change any character in an input file to another character in the output file within a specified field or throughout the entire record. The ALTSEQ statement is used to specify the from and to characters, and TRAN=ALTSEQ is used to specify which field or fields the ALTSEQ changes are to be applied to. Here's how you could change all low values (X'00') to spaces (X'40'), in an FB data set with an LRECL of 60: ALTSEQ CODE=( 4 ) OUTREC FIELDS=(1,6 ,TRAN=ALTSEQ) Here's how you could change all 'a' (X'81') and 'x' (X'A7') characters to 'b' (X'82') and 'y' (X'A8') characters, respectively, in a VB input data set with any LRECL: ALTSEQ CODE=(81A7,82A8) OUTREC FIELDS=(1,4,5,TRAN=ALTSEQ) Of course, you can make your changes to specified fields instead of to the entire record. This comes in handy when you have mixed character and numeric fields in your records and want to avoid making changes to the numeric fields. For example, if you had an FB input file that had characters in bytes 1-20, a PD field in bytes 21-25, and characters in bytes 26-80, you could changes zeros to spaces in the character fields using: ALTSEQ CODE=( 4 ) OUTREC FIELDS=(1,2 ,TRAN=ALTSEQ, 21,5, 26,55,TRAN=ALTSEQ) CH - change zeros to spaces PD field - no change CH - change zeros to spaces
By not using TRAN=ALTSEQ for the PD field, we avoid changing PD values incorrectly, such as from X'000000001C' (P'1') to X'404040401C' (P'404040401').
51
SORT FIELDS=(1,6,CH,A),FORMAT=CH OUTREC FIELDS=(1:DATE=(4MD-),11:1,6) You could insert the current time as well as the current date in your records to produce a timestamp. For example: OUTREC FIELDS=(DATE3,TIME1,1,6) would produce a character timestamp in output positions 1-12 of the form: yyyydddhhmmss Date constants can be produced in a variety of other character, zoned decimal and packed decimal formats as well such as C'yyyy-mm', Z'yyyymmdd' and P'yyddd'. Time constants can also be produced in a variety of other character, zoned decimal and packed decimal formats as well such as C'hh:mm', Z'hhmmssxx' and P'hhmmss'. If, as in the second question above, you wanted to produce just one record containing the date, you could select from a variety of date formats. For example, if you wanted to create a record with just C'dddyy', you could do it with OUTREC as follows: //DATERCD EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DUMMY RECORD //SORTOUT DD DSN=... //SYSIN DD OPTION COPY OUTREC FIELDS=(YDDDNS=(DY)) /
Of course, you could change the case in the entire record as well. For example, here's how you could change uppercase to lowercase in the records of an FB data set with an LRECL of 200: OUTREC FIELDS=(1,2 ,TRAN=UTOL)
And here's how you could change uppercase to lowercase in the records of a VB data set with any LRECL: OUTREC FIELDS=(1,4,5,TRAN=UTOL)
52
53
POSITION,RVAR_SPECIFIED RVAR_SPEC8 ,,8 ,CH POSITION,PERM_SPECIFIED PERM_SPEC8 ,,8 ,CH POSITION,PERM_RES_NAME PERM_RNAME8 ,,8 ,CH POSITION,AD_SPECIFIED AD_SPEC8 ,,8 ,CH POSITION,PWD_SPECIFIED PWD_SPEC8 ,,8 ,CH POSITION,ACC_RES_NAME ACC_RNAME8 ,,8 ,CH / //TEMP1 DD DSN=&&T1,DISP=(,PASS),SPACE=(CYL,(2,5),RLSE),UNIT=SYSDA //REPORT DD SYSOUT= //TOOLIN DD Use DFSORT control statements in CTL1CNTL to select only the records for users with SPECIAL, reformat different event type records to a common format for the report, and sort the selected/reformatted records by the User Id field. SORT FROM(ADUDATA) TO(TEMP1) USING(CTL1) Print the report from the selected/reformatted records. DISPLAY FROM(TEMP1) LIST(REPORT) BLANK PAGE TITLE('Commands Executed Due to the SPECIAL Attribute') DATE(4MD/) TIME(12:) ON(RPT_TIME) HEADER('Time') ON(RPT_DATE) HEADER('Date') ON(RPT_USERID) HEADER('User ID') ON(RPT_SYSTEM) HEADER('System') ON(RPT_USERNAME) HEADER('User Name') ON(RPT_EVENT) HEADER('Event') ON(RPT_PROF8 ) HEADER('Command Target') ON(RPT_SPEC8 ) HEADER('Keywords Specified') / //CTL1CNTL DD Select only the records for users with SPECIAL. INCLUDE COND=(ACC_AUTH_SPECIAL,EQ,C'YES') Reformat different event type records to a common format for the report. INREC IFTHEN=(WHEN=(PERM_EVENT_TYPE,EQ,C'PERMIT'), BUILD=(RDW,5:PERM_TIME_WRITTEN,13:PERM_DATE_WRITTEN, 23:PERM_EVT_USER_ID,31:PERM_SYSTEM_SMFID,35:PERM_EVENT_TYPE, 43:PERM_USER_NAME, 63:PERM_RNAME8 ,143:PERM_SPEC8 )), IFTHEN=(WHEN=(AU_EVENT_TYPE,EQ,C'ADDUSER'), BUILD=(RDW,5:AU_TIME_WRITTEN,13:AU_DATE_WRITTEN, 23:AU_EVT_USER_ID,31:AU_SYSTEM_SMFID,35:AU_EVENT_TYPE, 43:AU_USER_NAME, 63:AU_USER_ID,143:AU_SPEC8 )), IFTHEN=(WHEN=(ALU_EVENT_TYPE,EQ,C'ALTUSER'), BUILD=(RDW,5:ALU_TIME_WRITTEN,13:ALU_DATE_WRITTEN, 23:ALU_EVT_USER_ID,31:ALU_SYSTEM_SMFID,35:ALU_EVENT_TYPE, 43:ALU_USER_NAME, 63:ALU_USER_ID,143:ALU_SPEC8 )), IFTHEN=(WHEN=(DELU_EVENT_TYPE,SS,EQ, C'DELUSER ,ADDGROUP,ALTGROUP,DELGROUP,CONNECT ,REMOVE '), BUILD=(RDW,5:DELU_TIME_WRITTEN,13:DELU_DATE_WRITTEN,
54
23:DELU_EVT_USER_ID,31:DELU_SYSTEM_SMFID,35:DELU_EVENT_TYPE, 43:DELU_USER_NAME, 63:DELU_USER_ID,143:DELU_SPEC8 )), IFTHEN=(WHEN=(RDEF_EVENT_TYPE,SS,EQ,C'RDEFINE ,RDELETE ,RALTER '), BUILD=(RDW,5:RDEF_TIME_WRITTEN,13:RDEF_DATE_WRITTEN, 23:RDEF_EVT_USER_ID,31:RDEF_SYSTEM_SMFID,35:RDEF_EVENT_TYPE, 43:RDEF_USER_NAME, 63:RDEF_RNAME8 ,143:RDEF_SPEC8 )), IFTHEN=(WHEN=(PWD_EVENT_TYPE,EQ,C'PASSWORD'), BUILD=(RDW,5:PWD_TIME_WRITTEN,13:PWD_DATE_WRITTEN, 23:PWD_EVT_USER_ID,31:PWD_SYSTEM_SMFID,35:PWD_EVENT_TYPE, 43:PWD_USER_NAME, 63:X,143:PWD_SPEC8 )), IFTHEN=(WHEN=(RVAR_EVENT_TYPE,SS,EQ,C'RVARY ,SETROPTS'), BUILD=(RDW,5:RVAR_TIME_WRITTEN,13:RVAR_DATE_WRITTEN, 23:RVAR_EVT_USER_ID,31:RVAR_SYSTEM_SMFID,35:RVAR_EVENT_TYPE, 43:RVAR_USER_NAME, 63:X,143:RVAR_SPEC8 )), IFTHEN=(WHEN=(ACC_EVENT_TYPE,EQ,C'ACCESS'), BUILD=(RDW,5:ACC_TIME_WRITTEN,13:ACC_DATE_WRITTEN, 23:ACC_EVT_USER_ID,31:ACC_SYSTEM_SMFID,35:ACC_EVENT_TYPE, 43:ACC_USER_NAME, 63:ACC_RNAME8 ,143:ACC_EVENT_QUAL)), IFTHEN=(WHEN=(AD_EVENT_TYPE,SS,EQ,C'ADDSD ,ALTDSD ,DELDSD '), BUILD=(RDW,5:AD_TIME_WRITTEN,13:AD_DATE_WRITTEN, 23:AD_EVT_USER_ID,31:AD_SYSTEM_SMFID,35:AD_EVENT_TYPE, 43:AD_USER_NAME, 63:AD_DS_NAME,143:AD_SPEC8 )) Sort the selected/reformatted records by the User Id field. SORT FIELDS=(RPT_USERID,A) / RACF "SPECIAL" report without DFSORT symbols //RPTNS EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //ADUDATA DD DSN=.... IRRADU data //TEMP1 DD DSN=&&T1,DISP=(,PASS),SPACE=(CYL,(2,5),RLSE),UNIT=SYSDA //REPORT DD SYSOUT= //TOOLIN DD Use DFSORT control statements in CTL1CNTL to select only the records for users with SPECIAL, reformat different event type records to a common format for the report, and sort the selected/reformatted records by the User Id field. SORT FROM(ADUDATA) TO(TEMP1) USING(CTL1) Print the report from the selected/reformatted records. DISPLAY FROM(TEMP1) LIST(REPORT) BLANK PAGE TITLE('Commands Executed Due to the SPECIAL Attribute') DATE(4MD/) TIME(12:) ON(5,8,CH) HEADER('Time') ON(13,1 ,CH) HEADER('Date') ON(23,8,CH) HEADER('User ID') ON(31,4,CH) HEADER('System') ON(43,2 ,CH) HEADER('User Name') ON(35,8,CH) HEADER('Event') ON(63,8 ,CH) HEADER('Command Target') Smart DFSORT Tricks
55
ON(143,8 ,CH) HEADER('Keywords Specified') / //CTL1CNTL DD Select only the records for users with SPECIAL. INCLUDE COND=(86,3,CH,EQ,C'YES') Reformat different event type records to a common format for the report. INREC IFTHEN=(WHEN=(5,8,CH,EQ,C'PERMIT'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:3 4,2 ,63:5 7,8 ,143:763,8 )), IFTHEN=(WHEN=(5,8,CH,EQ,C'ADDUSER'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:295,2 ,63:5 8,8,143:517,8 )), IFTHEN=(WHEN=(5,8,CH,EQ,C'ALTUSER'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:295,2 ,63:522,8,143:531,8 )), IFTHEN=(WHEN=(5,8,SS,EQ, C'DELUSER ,ADDGROUP,ALTGROUP,DELGROUP,CONNECT ,REMOVE '), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:295,2 ,63:498,8,143:5 7,8 )), IFTHEN=(WHEN=(5,8,SS,EQ,C'RDEFINE ,RDELETE ,RALTER '), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:3 4,2 ,63:516,8 ,143:772,8 )), IFTHEN=(WHEN=(5,8,CH,EQ,C'PASSWORD'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:295,2 ,63:X,143:498,8 )), IFTHEN=(WHEN=(5,8,SS,EQ,C'RVARY ,SETROPTS'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:286,2 ,63:X,143:489,8 )), IFTHEN=(WHEN=(5,8,CH,EQ,C'ACCESS'), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:1126,2 ,63:286,8 ,143:14,8)), IFTHEN=(WHEN=(5,8,SS,EQ,C'ADDSD ,ALTDSD ,DELDSD '), BUILD=(1,4,5:23,8,13:32,1 ,23:63,8,31:43,4,35:5,8, 43:295,2 ,63:524,44,143:569,8 )) Sort the selected/reformatted records by the User Id field. SORT FIELDS=(23,8,CH,A) /
Multiple output records from some (but not all) input records
A customer asked us the following question: I have an FBA input file with an LRECL of 121. It has some records with a '1' (eject) as the ASA carriage control character, followed by data. I want to replace each such record with three records as follows: A record with a '1' (eject) in column 1 followed by blanks A record with a blank (newline) in column 1 followed by blanks A record with a blank (newline) in column 1 followed by the data from the input record I want to keep records that don't have a '1' in column 1 as is. The output should be FBA with an LRECL of 121. For example, if my input file contained the following records:
56
1 2 3 4 5
I'd want my output file to contain: 1 data data data 1 data data 4 5 1 2 3
Can you help? You can use an IFTHEN clause in an OUTFIL statement to do this quite easily. Here's an example of a DFSORT job to create the output file: //MULT EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... INPUT FILE //SORTOUT DD DSN=... OUTPUT FILE //SYSIN DD CREATE 3 OUTPUT RECORDS FOR EACH CC '1' INPUT RECORD. COPY EACH NON CC '1' RECORD AS IS. OPTION COPY OUTFIL IFTHEN=(WHEN=(1,1,CH,EQ,C'1'), CC IS '1' BUILD=(C'1',12 X,/, '1',BLANKS 121X,/, ' ',BLANKS X,2,12 )) ' ',DATA /
57
Non-leading blanks, as for example the fourth character in the 7 S5 record, should not be replaced by zeros. So the output file should look like this: 1XX 2 4121 7 S5 2 44X X44 7 2 4111 X8768978 25 3 XX46464 646 5665 FF FDFS ABC 1 QRSTUV 54 X 62 Can I use DFSORT or ICETOOL to do this? Using DFSORT's IFTHEN clauses, we can check for 6-1 leading blanks and replace them with 6-1 leading zeros, respectively, as follows: //S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD OPTION COPY INREC IFTHEN=(WHEN=(1,6,CH,EQ,C' IFTHEN=(WHEN=(1,5,CH,EQ,C' IFTHEN=(WHEN=(1,4,CH,EQ,C' IFTHEN=(WHEN=(1,3,CH,EQ,C' IFTHEN=(WHEN=(1,2,CH,EQ,C' IFTHEN=(WHEN=(1,1,CH,EQ,C' /
'),OVERLAY=(6C' ')), '),OVERLAY=(5C' ')), '),OVERLAY=(4C' ')), '),OVERLAY=(3C' ')), '),OVERLAY=(2C' ')), '),OVERLAY=(C' '))
so I'd want my output file to have the records from RUN001.OUTPUT, RUN001.RESULTS, RUN005.OUTPUTand RUN005.RESULTS in that order. Can DFSORT help me do this? We can copy the input data sets with an ICEGENER JOB like the following:
58
//CPYFILES JOB (XXX, 5),'PRGMR',CLASS=A,MSGCLASS=H, // MSGLEVEL=(1,1),TIME=(,15) //S1 EXEC PGM=ICEGENER //SYSPRINT DD SYSOUT= //SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD DUMMY //SYSUT1 DD DISP=SHR,DSN=file1 // DD DISP=SHR,DSN=file2 ... // DD DISP=SHR,DSN=filen But since we have to get the file1, file2, ..., filen names from the data set containing the list of file names, we'll actually have to generate the JCL shown above and have the internal reader submit it for execution. That's the tricky part, but here's what we'll do: We'll generate all of the JCL statements, except for the SYSUT1 concatenation statements, with OUTFIL's HEADER1 operand. The HEADER1 output is written before the data records and can contain as many output records as you like (/ is used to start a new record). Since HEADER1 is a report parameter, the report data set is normally given a RECFM of FBA and a 1-byte ANSI carriage control character (for example, '1' for page eject) is placed in position 1 of the report. For our purposes, we don't want that ANSI character in position 1 because it would give us invalid JCL (e.g. '1//CPYFILES ...' instead of '//CPYFILES ...". So we'll use OUTFIL's REMOVECC operand remove the ANSI character from each JCL statement we generate. We'll generate the SYSUT1 concatenation statements with OUTFIL's OUTREC operand. We'll change the file name in positions 1-44 of each input record into a DD statement referencing that file name. But as shown in the JCL we're trying to generate above, we need 'SYSUT1' as the ddname for the first DD and blanks as the ddname for the second and subsequent DD. To make this happen, we'll use an OUTREC statement with a SEQNUM operand to add a sequence number to the input records, and then use CHANGE in OUTFIL OUTREC to determine if the sequence number is 1 or not so we can set up the correct ddname. Here's our DFSORT job to generate the ICEGENER JCL we need and have the internal reader submit it:
59
//GENJOB EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... list of file names //IRDR DD SYSOUT=(A,INTRDR) internal reader //SYSIN DD OPTION COPY Add sequence numbers to file list so we can identify the first file in the list and use '//SYSUT1 DD' for it. We'll use '// DD' for the second and subsequent files in the list. OUTREC FIELDS=(1,44, file name from list 51:SEQNUM,3,ZD) sequence number Generate the JCL for output to the internal reader OUTFIL FNAMES=IRDR, Remove the carriage control character from the "report". REMOVECC, Generate JOB, EXEC, SYSPRINT, SYSUT2 and SYSIN statements. HEADER1=('//CPYFILES JOB (XXX, 5),''PRGMR'',', 'CLASS=A,MSGCLASS=H,',/, '// MSGLEVEL=(1,1),TIME=(,15)',/, '//S1 EXEC PGM=ICEGENER',/, '//SYSPRINT DD SYSOUT=',/, '//SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),', 'UNIT=SYSDA',/, '//SYSIN DD DUMMY'), Generate //SYSUT1 DD DISP=SHR,DSN=name1 // DD DISP=SHR,DSN=name2 ... OUTREC=(C'//', 51,3,CHANGE=(6,C' 1',C'SYSUT1'), 'SYSUT1' for first name NOMATCH=(C' '), blanks for subsequent names C' DD DISP=SHR,DSN=', 1,44, file name from list 8 :X) ensure record length is 8 / If you'd rather not encode your JOB, etc statements as constants, you can do the same thing in several DFSORT steps that we did above in one DFSORT step, like this: //GENJCL1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DATA,DLM=$$ //CPYFILES JOB (XXX, 5),'PRGMR',CLASS=A,MSGCLASS=H, // MSGLEVEL=(1,1),TIME=(,15) //S1 EXEC PGM=ICEGENER //SYSPRINT DD SYSOUT= //SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD DUMMY $$ //SORTOUT DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(1,1)),DISP=(,PASS) //SYSIN DD Copy JOB, EXEC, SYSPRINT, SYSUT2 and SYSIN statements. OPTION COPY / //GENJCL2 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SYSUDUMP DD SYSOUT=
60
//SORTIN DD DSN=... list of file names //TEMP DD DSN=&T1,DISP=(MOD,PASS) //SYSIN DD OPTION COPY Add sequence numbers to file list so we can identify the first file in the list and use '//SYSUT1 DD' for it. We'll use '// DD' for the second and subsequent files in the list. OUTREC FIELDS=(1,44, file name from list 51:SEQNUM,3,ZD) sequence number Generate //SYSUT1 DD DISP=SHR,DSN=name1 // DD DISP=SHR,DSN=name2 ... OUTFIL FNAMES=TEMP, OUTREC=(C'//', 51,3,CHANGE=(6,C' 1',C'SYSUT1'), 'SYSUT1' for first name NOMATCH=(C' '), blanks for subsequent names C' DD DISP=SHR,DSN=', 1,44, file name from list 8 :X) ensure record length is 8 / //SUBJCL EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=&T1,DISP=(OLD,PASS) //SORTOUT DD SYSOUT=(A,INTRDR) internal reader //SYSIN DD Submit the JCL to the internal reader OPTION COPY /
And here's what I'd want the sorted output to look like: (Key) 5 ABCDGRC 5 1ABCDGRC 5 2ABCDGRC ( 52 total) 2246K 2 1957211 B 2638364444R ( 53 total) 828784K 121859976B 131222671P ( 54 total) 155H 5152 8642F 824793989R (grand total) 83 874O 265664 729{ 35943811 6N
Note that the (headings) shown above are just for reference; they shouldn't actually appear in the output data set.
61
I know an indirect method of doing this which will take 5 passes over the data, but is there a way to do it in a single pass with DFSORT? The trick here is to use IFTHEN clauses to reformat the records for each type of key (052, 053 and 054) with slots for all of the keys and the grand total. The actual key will go into its slot and the grand total slot, and zeros will go into the slots for the other keys. That will allow us to use SUM to produce the key totals and grand totals. Here's a DFSORT job that produces the requested output: //S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD Reformat 52 records with a slot for 52, 53, 54 and grand total. 52 and total will have the 52 values. Others will be Z' '. INREC IFTHEN=(WHEN=(1,3,CH,EQ,C' 52'), BUILD=(1:4,1 ,16:19,12,3 :12C' ',44:12C' ',58:19,12)), Reformat 53 records with a slot for 52, 53, 54 and grand total. 53 and total will have the 53 values. Others will be Z' '. IFTHEN=(WHEN=(1,3,CH,EQ,C' 53'), BUILD=(1:4,1 ,16:12C' ',3 :19,12,44:12C' ',58:19,12)), Reformat 54 records with a slot for 52, 53, 54 and grand total. 54 and total will have the 54 values. Others will be Z' '. IFTHEN=(WHEN=(1,3,CH,EQ,C' 54'), BUILD=(1:4,1 ,16:12C' ',3 :12C' ',44:19,12,58:19,12)) Sort on the key SORT FIELDS=(1,1 ,CH,A) Sum the 52, 53, 54 and grand total fields. SUM FORMAT=ZD,FIELDS=(16,12,3 ,12,44,12,58,12) /
Omit data set names with Axxx. as the high level qualifier
Radoslaw Skorupka kindly contributed this trick. Here's what it does: This DFSORT job omits certain data set names from a list. The data sets to be omitted are those that start with 'Axxx.' (where x is any character), except data sets that start with 'ASMA.' or 'ASMT.' are to be kept. For example, if the input file contained the following records: ASMT.DASD BILL.MASTER ALL.DASD A123.MASTER.IN ALLOC.DASD ASMQ.ALL.FILES SYS1.LINKLIB A1.MYFILE ASMA.MYFILE ALEX.FILE1.OUT We'd want the output file to contain:
62
ASMT.DASD BILL.MASTER ALL.DASD ALLOC.DASD SYS1.LINKLIB A1.MYFILE ASMA.MYFILE Here's Radoslaw's job: //S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... INPUT FILE //SORTOUT DD DSN=... OUTPUT FILE //SYSIN DD OPTION COPY OMIT FORMAT=CH, COND=((1,5,NE,C'ASMA.',AND, 1,5,NE,C'ASMT.'),AND, (1,1,EQ,C'A',AND, 2,1,NE,C'.',AND, 3,1,NE,C'.',AND, 4,1,NE,C'.',AND, 5,1,EQ,C'.')) /
All fields have CH format Keep if 'ASMA.' Keep if 'ASMT.' Keep if not 'A' Keep if 'A.' Keep if 'Ax.' Keep if 'Axx.' Omit if 'Axxx.'
63
//DRPT EXEC PGM=ICEMAN //SYSOUT DD SYSOUT= //SORTIN DD DSN=... DCOLLECT file //DSNOUT DD SYSOUT= Report //SYSIN DD Sort by the HLQ SORT FIELDS=(29,4,CH,A) Include only D-type records INCLUDE COND=(9,1,CH,EQ,C'D',AND, 29,4,SS,EQ,C'PAK#,PAL#,PAN#') Create report OUTFIL FNAMES=DSNOUT, NODETAIL, HEADER2=(1:PAGE,12:DATE,24:'BREAKDOWN BY HLQ',/, 1:1 X,/, 1:'HLQ',14:'# DATASETS',28:'SPACE USED (Mb)',/, 1:'---------',14:'----------',28:'---------------'), SECTIONS=(29,4, TRAILER3=(1:29,4,16:COUNT,33:TOT=(93,4,BI,M1 )))
64
65
DCOLLECT VOLUMES() NODATAINFO OUTFILE(OUT) ADD NAME=SORT INCLUDE COND=(9,1,CH,EQ,C'V') SORT FIELDS=(81,2,BI,A) OUTFIL FNAMES=SORTOUT,CONVERT,OUTREC=(1: 1C' SETCACHE VOLUME(', 29,6,1C') UNIT(339 ) DASDFASTWRITE ON / ', 81,2,66:15X) OUTFIL FNAMES=SORTOUT2,CONVERT,OUTREC=(1: 1C' SETCACHE VOLUME(', 29,6,1C') UNIT(339 ) DEVICE ON / ', 81,2,66:15X) OUTFIL FNAMES=SORTOUT3,INCLUDE=(82,1,BI,NONE,X' F'), CONVERT,OUTREC=(1: 1C' SETCACHE VOLUME(', 29,6,1C') UNIT(339 ) SUBSYSTEM ON / ', 81,2,66:15X) OUTFIL FNAMES=SORTOUT4,INCLUDE=(82,1,BI,NONE,X' F'), CONVERT,OUTREC=(1: 1C' SETCACHE VOLUME(', 29,6,1C') UNIT(339 ) NVS ON / ', 81,2,66:15X) ./ ADD NAME=MERGE MERGE FIELDS=(59,4,BI,A,38,6,CH,D) OUTFIL FNAMES=SORTOUT,OUTREC=(1,58,59,2,HEX, 1C' /',66:15X) / //STEP2 EXEC PGM=IDCAMS,REGION=6M // // RUN DCOLLECT ONLY VOLUME INFORMATION // //OUT DD DSN=&&DOUT,DISP=(NEW,PASS), // SPACE=(TRK,(15,5),RLSE), // DSORG=PS,RECFM=VB,LRECL=644,BLKSIZE= //SYSPRINT DD SYSOUT= //SYSIN DD DSN=&&PDS(DCOLLECT),DISP=(OLD,PASS) //STEP3 EXEC PGM=SORT,REGION=6M // // ONLY V RECORDS - OUTPUT VOLSER AND UNIT ADDRESS HEX // //SYSOUT DD SYSOUT= //SORTIN DD DSN=&&DOUT,DISP=(OLD,PASS) //SORTOUT DD DSN=&&SORT1,DISP=(NEW,PASS), // SPACE=(TRK,15),LRECL=8 ,BLKSIZE= ,RECFM=FB //SORTOUT2 DD DSN=&&SORT2,DISP=(NEW,PASS), // SPACE=(TRK,15),LRECL=8 ,BLKSIZE= ,RECFM=FB //SORTOUT3 DD DSN=&&SORT3,DISP=(NEW,PASS), // SPACE=(TRK,15),LRECL=8 ,BLKSIZE= ,RECFM=FB //SORTOUT4 DD DSN=&&SORT4,DISP=(NEW,PASS), // SPACE=(TRK,15),LRECL=8 ,BLKSIZE= ,RECFM=FB //SYSIN DD DSN=&&PDS(SORT),DISP=(OLD,PASS) //STEP4 EXEC PGM=SORT,REGION=6M // // MERGE IDCAMS CONTROL STATEMENTS // //SYSOUT DD SYSOUT= //SORTIN3 DD DSN=&&SORT1,DISP=(OLD,DELETE) //SORTIN4 DD DSN=&&SORT2,DISP=(OLD,DELETE) ./
66
//SORTIN1 DD DSN=&&SORT3,DISP=(OLD,DELETE) //SORTIN2 DD DSN=&&SORT4,DISP=(OLD,DELETE) //SORTOUT DD DSN=&&SET,DISP=(NEW,PASS), // SPACE=(TRK,15),LRECL=8 ,BLKSIZE= ,RECFM=FB //SYSIN DD DSN=&&PDS(MERGE),DISP=(OLD,PASS) // // EXECUTE THE GENERATED IDCAMS CONTROL STATEMENTS // //STEP5 EXEC PGM=IDCAMS,REGION=6M //SYSPRINT DD SYSOUT= //SYSIN DD DSN=&&SET,DISP=(OLD,DELETE) Here's a sample of what the generated IDCAMS control statements might look like: . . . SETCACHE SETCACHE SETCACHE SETCACHE SETCACHE SETCACHE . . . VOLUME(PPPACK) VOLUME(PPPACK) VOLUME(USRPAK) VOLUME(USRPAK) VOLUME(1P 3 1) VOLUME(1P 3 1) UNIT(339 UNIT(339 UNIT(339 UNIT(339 UNIT(339 UNIT(339 ) ) ) ) ) ) DEVICE DASDFASTWRITE DEVICE DASDFASTWRITE DEVICE DASDFASTWRITE ON ON ON ON ON ON / / / / / / 8 3 8 3 8 8 8 8 813 813 / / / / / /
67
//SORTRUN EXEC PGM=DFSC //STEPLIB DD DSN=... //STDIN DD DUMMY //STDOUT DD SYSOUT= //STDERR DD SYSOUT= //SYSPRINT DD SYSOUT= //SYSOUT DD SYSOUT= //SORTIN DD DSN=... //SORTOUT DD DSN=... //SYSIN DD SORT FIELDS=(5,2,CH,A) OMIT COND=(1 ,4,CH,EQ,C'DATA') / Here's a simple example of a C program that calls ICETOOL: / This example illustrates how to use SYSTEM() to call ICETOOL. #include <stdlib.h> #include <stdio.h> main() { int toolrc; toolrc = system("PGM=ICETOOL"); if (toolrc >= 12) printf("ICETOOL failed - RC=%d", toolrc); else printf("ICETOOL completed successfully - RC=%d", toolrc); return(toolrc); } and here's the JCL you might use to execute this program: //TOOLRUN EXEC PGM=TOOLC //STEPLIB DD DSN=... //STDIN DD DUMMY //STDOUT DD SYSOUT= //STDERR DD SYSOUT= //SYSPRINT DD SYSOUT= //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //IN DD DSN=... //OUT1 DD DSN=... //OUT2 DD DSN=... //TOOLIN DD SELECT FROM(IN) TO(OUT1) ON(5,2,CH) LAST SELECT FROM(IN) TO(OUT2) ON(21,5,ZD) HIGHER(4) / /
68
69
Questioning the customer, we established that each account number only appeared once in the Master file and once in the Pull file. Given that, we were able to come up with a smart DFSORT trick using the FIRSTDUP operand of ICETOOL's SELECT operator. Here's the ICETOOL job that does the trick. The Pull file contains the account numbers to "pull" from the Master file. For simplicity, we're assuming both files are fixed-length, although the trick would work for variable-length files with some minor adjustments. //PULLRCDS JOB ... //SUPULL EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= //DFSMSG DD SYSOUT= //PULL DD DSN=... the Pull file //TPULL DD DSN=&T1,UNIT=SYSDA,DISP=(,PASS),SPACE=(CYL,(5,5)) //CONCAT DD DSN=... Master file // DD DSN=.TPULL,VOL=REF=.TPULL,DISP=(OLD,PASS) //OUTPUT DD DSN=... Output file with "pulled" records //TOOLIN DD Reformat the Pull file so the account number is in the same place as in the Master file COPY FROM(PULL) TO(TPULL) USING(CPY1) SELECT the records in the Master File that are also in the PULL file x and n are as above SELECT FROM(CONCAT) TO(OUTPUT) ON(x,n,CH) FIRSTDUP //CPY1CNTL DD In the OUTREC statement below: x = position of account number in Master file y = position of account number in Pull file n = length of account number m = LRECL of Master File OUTREC FIELDS=(x:y,n,m:X) / There are a couple of tricks here that merit explanation: In order to use SELECT with FIRSTDUP, the keys for both files must be in the same place. We use a COPY operator with an OUTREC statement to create a temporary copy of the Pull file that has the account number in the same place as in the Master file. We concatenate the Master file and the temporary Pull file together, in that order, and use SELECT with FIRSTDUP to keep only the records from the Master file with account numbers that appear in both the Master file and the temporary Pull file. Tricky, but effective :-)
70
Job 1 originally consisted of the following steps: Step 1: 3 sec - An alternate index is deleted and defined. Step 2: 35 min - An application program is used to extract records from a VSAM cluster and write them to a tape data set which is sent offsite. Step 3: 41 min - DFSORT is used to sort the extracted data set to a DASD data set. Step 4: 16 min - REPRO is used to load the sorted DASD data set into the alternate index. Our phase 1 solution was to eliminate the REPRO step. We changed step 3 to have DFSORT sort the extracted data set directly into the alternative index. Eliminating step 4 saved about 16 minutes. Our phase 2 solution was to replace the application program in step 2 with a DFSORT step which extracted the needed records (using INCLUDE), sorted them and used OUTFIL to write the records concurrently to the alternate index and tape data set. By redesigning the job, we: Reduced the elapsed time, CPU time and EXCPs significantly. Eliminated the application program and its associated maintenance. Eliminated the DASD intermediate data set. Reduced the number of tape mounts. Job 2 consisted of the following steps: Step 1: 3 sec - A VSAM cluster is deleted and defined. Step 2: 16 min - DFSORT is used to sort the output of a batch process to a DASD data set. Step 3: 13 min - DFSORT is used to copy the sorted DASD data set to a tape data set which is sent offsite. Step 4: 10 min - REPRO is used to load the sorted DASD data set into the VSAM cluster. Our solution was to use DFSORT's OUTFIL to write the tape data set and load the VSAM cluster concurrently. By redesigning the job, we reduced the elapsed time, CPU time and EXCPs significantly and eliminated the DASD intermediate data set. Here's a job similar to the revised Job 2 described above:
71
//LDVKSDS JOB ... //CREATEIT EXEC PGM=IDCAMS,REGION= M //SYSPRINT DD SYSOUT= //SYSIN DD DELETE userid.VKSDS PURGE CLUSTER IF MAXCC = 8 THEN SET MAXCC = DEFINE / DEFINE THE MAIN DB CLUSTER(NAME(userid.VKSDS) DATACLASS(COMV) / SET DATACLASS CISZ(24576) / MIN VALUE IS 1 24 CYLINDERS(5 2 ) / LIMITS THE DB SIZE RECSZ(15 15 ) FSPC(1 1) / VAR. KSDS, FREE SPACE KEYS(1 ) / KEY SHR(2) IXD SPEED NOREUSE NREPL / PARAMETERS BUFSPC(1 )) / BUFFER SPACE DATA(NAME(userid.VKSDS.DATA)) INDEX(NAME(userid.VKSDS.INDEX) CISZ(4 96)) / // //LOADIT EXEC PGM=ICEMAN,REGION=8192K //SYSPRINT DD SYSOUT= //SYSOUT DD SYSOUT= //SORTIN DD DSN=userid.VINPUT,DISP=SHR //KSDSOUT DD DSN=userid.VKSDS,DISP=OLD //TAPEOUT DD DSN=userid.VTAPE,... //SYSIN DD SORT FIELDS=(5,1 ,BI,A) OUTFIL FNAMES=(KSDSOUT,TAPEOUT) / Important note:
/ / / / / / / / -
The DEFINE CLUSTER has KEYS(100 0), but the SORT statement has FIELDS=(5,100,...). 100 is the length in both cases, but why does the key start at 0 for the DEFINE CLUSTER and at 5 for the SORT statement? Two reasons: 1. DEFINE CLUSTER uses an offset for the key whereas DFSORT uses a position for the key. Position 1 is equivalent to offset 0. 2. The SORTIN data set is variable-length so its records start with a 4-byte RDW. Thus, from DFSORT's point of view, the key starts at position 5 after the RDW. DFSORT removes the RDW when it writes the record to the VSAM data set. If the SORTIN data set was fixed-length, its records would not have RDWs and the key would start at position 1 from DFSORT's point of view.
72
To make the reports easy to read, OUTFIL's lookup and change feature is used to assign English descriptions to DCOLLECT's bit flags. The Storage Administrator Examples (ICESTGEX) available on the DFSORT product tape contains this example as well as many others. //DCOLEX2 JOB ... // // DCOLLECT EXAMPLE 2: CONVERSION REPORTS // ASSIGN ENGLISH DESCRIPTIONS TO BIT FLAGS TO SHOW MIGRATION // STATUS OF VOLUMES (IN CONVERSION, MANAGED BY SMS AND // NON-SMS MANAGED). // //STEP1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT= ICETOOL MESSAGES //DFSMSG DD SYSOUT= DFSORT MESSAGES //TOOLIN DD CONTROL STATEMENTS PART 1 - ADD IDENTIFYING STATUS (FLAG) DESCRIPTION FOR 'MANAGED', 'IN CONVERSION' AND 'NON-MANAGED' VOLUME RECORDS COPY FROM(DCOLALL) USING(FLAG) PART 2 - PRINT REPORT SHOWING COUNT OF EACH STATUS TYPE OCCUR FROM(STATUS) LIST(REPORTS) TITLE('STATUS COUNT REPORT') DATE BLANK HEADER('STATUS') ON(7,2 ,CH) HEADER('NUMBER OF VOLUMES') ON(VALCNT) PART 3 - PRINT REPORT SORTED BY VOLUMES AND STATUS SORT FROM(STATUS) TO(SRTVOUT) USING(SRTV) DISPLAY FROM(SRTVOUT) LIST(REPORTV) TITLE('VOLUME/STATUS REPORT') DATE PAGE BLANK HEADER('VOLUME') ON(1,6,CH) HEADER('STATUS') ON(7,2 ,CH) PART 4 - PRINT REPORT SORTED BY STATUS AND VOLUMES SORT FROM(STATUS) TO(SRTIOUT) USING(SRTI) DISPLAY FROM(SRTIOUT) LIST(REPORTI) TITLE('STATUS/VOLUME REPORT') DATE PAGE BLANK HEADER('STATUS') ON(7,2 ,CH) HEADER('VOLUME') ON(1,6,CH) //DCOLALL DD DSN=Y176398.R12.DCOLLECT,DISP=SHR //STATUS DD DSN=&&TEMP1,DISP=(,PASS),UNIT=SYSDA, // LRECL=5 ,RECFM=FB,DSORG=PS //SRTVOUT DD DSN=&&TEMP2,DISP=(,PASS),UNIT=SYSDA //SRTIOUT DD DSN=&&TEMP3,DISP=(,PASS),UNIT=SYSDA //FLAGCNTL DD FIND V-TYPE RECORDS WITH STATUS FLAGS OF INTEREST INCLUDE COND=(9,2,CH,EQ,C'V ',AND, 35,1,BI,NE,B'......1 ') CREATE RECFM=FB OUTPUT RECORDS WITH VOLSER AND STATUS DESCRIPTION. LOOKUP/CHANGE TABLE FLAG DESCRIPTION ----------------------------- DCVMANGD MANAGED BY SMS DCVINITL IN CONVERSION TO SMS DCVNMNGD NON-SMS MANAGED
Smart DFSORT Tricks
73
OUTFIL FNAMES=STATUS,CONVERT, OUTREC=(29,6, 35,1,CHANGE=(2 , B'......11',C'MANAGED BY SMS', B'...... 1',C'IN CONVERSION TO SMS', B'...... ',C'NON-SMS MANAGED'), 5 :X) //REPORTS DD SYSOUT= //SRTVCNTL DD SORT BY VOLUME AND INDENTIFYING STATUS STRING SORT FIELDS=(1,6,CH,A,7,2 ,CH,A) //SRTICNTL DD SORT BY INDENTIFYING STATUS STRING AND VOLUME SORT FIELDS=(7,2 ,CH,A,1,6,CH,A) //REPORTV DD SYSOUT= //REPORTI DD SYSOUT= // The first report is produced using ICETOOL's OCCUR operator. It shows the number of volumes in each of the three status classes. Here's what it might look like: STATUS COUNT REPORT STATUS -------------------IN CONVERSION TO SMS MANAGED BY SMS NON-SMS MANAGED 9/ 5/96 NUMBER OF VOLUMES ----------------3 16 1
The second report is produced using ICETOOL's DISPLAY operator. It shows the volumes in sorted order and the status of each. Here's what a portion of this report might look like: VOLUME/STATUS REPORT VOLUME -----SHR2 SHR2 1 SHR2 2 . . . SMS2 SMS2 1 . . . SHR287 . . . STATUS -------------------NON-SMS MANAGED NON-SMS MANAGED NON-SMS MANAGED 9/ 5/96 -1-
IN CONVERSION STATUS
The third report is another view of the data in the second report, obtained by switching the order of the fields. It shows the status classes in sorted order and each volume in that status class. Here's what a portion of this report might look like:
74
STATUS/VOLUME REPORT STATUS -------------------IN CONVERSION STATUS . . . MANAGED BY SMS MANAGED BY SMS . . . NON-SMS MANAGED NON-SMS MANAGED NON-SMS MANAGED . . . VOLUME -----SHR287
9/ 5/96
-1-
SMS2 SMS2 1
75