In the previous chapter you were introduced to the basic
principles and syntax of Jazz, ending up with a program that produced a report
by going through a sequential file. As
it went though this file records from a VSAM file were read by key so that the
Region Name, not just the code value, could be included in the report.
In this
chapter we extend your Jazz skills by showing you how to write batch programs
that will create and update files. It is
probable that you’ll also want to read the next chapter, Jazz Logic, as in this chapter we’ll deal with only
very simple situations. The two chapters
would have been combined except that Jazz Logic is equally applicable to batch
and online programs, whereas this chapter only applies to batch programming. Later chapters will show you how to write on
line programs, either Classical CICS (3270-style) or Web Services. This chapter focuses on “What can you do with
batch programs?”
Direct-Access
Updating by Batch Programs
Suppose
that we want to create a copy of some of the records from a file. Typical scenarios:
·
We
want to reformat a file, say from sequential to VSAM or SQL
·
We
want to create a small file for development and testing
·
We
want to change record formats – omitting some fields, changing the format of
others, calculating values etc
We can do
this with a program that is very similar to the report program that we wrote in
the previous chapter, but with a WRITE statement rather than PRINT. For example, here we are creating a simple
file from some data that we have created using TSO. We had created a number of records in a user
library: -
This data
is described by record layout In1A: -
and we can
print the data with a program like this: -
Here is the
printout: -
Printed at 17 Oct 2014,
20:21:34 Report1 Page 1
Region District *----Name-----*
SalesThisMonth SalesYTD Cardholder DateCommenced
09
03 CustomerName2** 00329.82
01092.63 N 2012-10-17
05 09 CustomerName3 00054.02 02370.49 N 2011-12-22
04 07 CustomerName4 00617.74 03218.90 N 2012-09-06
08 06 CustomerName5 00226.77 02907.83 N 2012-11-16
02 04 CustomerName6 00805.15 05062.13 N 2012-03-15
03 08 CustomerName7 00429.18 02063.06 N 2011-10-07
09 03 CustomerName8 00787.30 05925.17 N 2013-06-04
04 10 CustomerName9 00201.13 00214.91 N 2012-05-13
08 07 CustomerName10 00691.30 06125.78 N 2011-08-24
09 05 CustomerName11 00931.61 04025.08 N 2012-08-15
04
09 CustomerName12 00054.88
03855.79 N 2011-12-20
09 06 CustomerName13 00071.01 03731.73 N 2013-05-26
05 02 CustomerName14 00172.80 05938.50 N 2012-05-17
(Continued
for several pages).
To load
this into a VB file with the fields converted to
more appropriate formats and omitting the unwanted FILLER is easy. We start by preparing a record definition of
the output format that we want: -
Now we
write a program to convert the record formats from In1a to In1. Logic is basically a PROCESS loop containing the statements: -
In1.* = IN1a.*;
WRITE in1;
so here is our complete program: -
PROGRAM PRDta1;
COPY in1a;
COPY In1;
PROCESS in1A;
PRINT
(IN1A.*);
In1.* = IN1a.*;
#207 I Name, SalesThisMonth,
SalesYTD, DateCommenced included in generic assignment
WRITE
in1;
#378 W Batch
WRITE used - you may need to edit the JCL
END PROCESS in1A;
Well,
almost!. Message #297 tells us that most,
but not all, of the fields that we want have been assigned by the generic
assignment. However we haven’t assigned the fields Region and District, because
in one record they are within a group but in the other they are not. If we ran the program now we’d find that all
the values of Region and District in the output file were initialised to zero,
not set to the values from the input records.
We therefore add two more assignment statement, and our program is complete:
-
Previously
we’ve written programs that PRINT data from an existing file. When we’d finished programming we just
clicked [Process] and Jazz did it all:
created the COBOL, and then created JCL and submitted a job to zOS to be
compiled and run. Provided that our
definitions contain a valid DSNAME option Jazz will generate JCL to
access the file and we don’t need to think about this.
However for
the run step Jazz will assume that the file already exists. Message #378 warns us that this might not be
true, or that the JCL may be incorrect for an output file: -
Sequential files must have correct DISP and
SPACE parameters.
VSAM files must have been created by IDCAMS,
and be empty. Records must be created in
sequence.
Tables must have been defined in SQL databases.
Instead of
simply clicking [Process] to have Jazz create COBOL and JCL and submit a job to
compile and run the program, we should interrupt this process. Right-click [Process] and then click [JCL]. Jazz will check the program, generate COBOL,
and then generate JCL. However the
processing stops there and the button [Review JCL] appears.
Click this
and you’ll see the JCL that will be submitted.
//IBMUSER6 JOB
,CLASS=A,MSGCLASS=H,NOTIFY=&SYSUID,COND=(8,LT)
//*** COMPILE BATCH PROGRAM
OR SUBPROGRAM
// SET MEMBER=CRDTA1
// SET SOURCE=IBMUSER.MANAJAZZ.SRCLIB
// SET COPYLIB=IBMUSER.MANAJAZZ.CPYLIB
//COMPILE EXEC IGYWCL
//COBOL.SYSIN DD
DSN=IBMUSER.MANAJAZZ.SRCLIB(CRDTA1),DISP=SHR
//COBOL.SYSLIB DD DSN=IBMUSER.MANAJAZZ.CPYLIB,DISP=SHR
//LKED.SYSLIB DD
// DD
// DD
DSN=IBMUSER.MANAJAZZ.LOADLIB,DISP=SHR
//LKED.SYSLMOD DD DSN=IBMUSER.MANAJAZZ.LOADLIB(CRDTA1),
// UNIT=,SPACE=
//*** RUN (RECENTLY
COMPILED) BATCH PROGRAM
//GO EXEC PGM=CRDTA1
//STEPLIB DD DSN=IBMUSER.MANAJAZZ.LOADLIB,DISP=SHR
//SYSOUT DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
//SORTLIB DD DSN=SYS1.SORTLIB,DISP=SHR
//SORTWK01 DD
UNIT=SYSDA,SPACE=(CYL,(20,5))
//* INSERTED DD STATEMENTS
BASED ON PROGRAM
//IN1A DD DSNAME=IBMUSER.MANAJAZZ.SRCLIB(DTDTA1),DISP=SHR
//IN1 DD DSNAME=IBMUSER.FILES.IN1,DISP=SHR
We must
correct the DD statements for the output file, in this case IN1. The changes we make depend on the file type.
Change this
to have DISP=(NEW,CATLG), and add a suitable SPACE parameter. You may also need to give other parameters
like VOL.
For VSAM the file should be created with the utility IDCAMS before you write data to it. For example, here is an IDCAMS job to create the Regions file that was used with MATCH FR WHERE … to get region names. This job not only creates the file (the DEFINE statement, lines 2001 and 2002), but it also copies initial data into the file with the REPRO statement, line 2003: -
With the dialog [JazzGen] Data/VSAM Jazz will create a suitable job for you from the record definition. See JazzWKVSAM.htm for more information.
As with VSAM, tables in an SQL database (DB2, ORACLE, or SQL-Server) must be created before they can be processed by Jazz. Refer to the chapter “Using Jazz with SQL” for more information.
Instead of reading and re-formatting a character file you may wish to simply create new records by program logic. Here I want to create three records in a test file, defined like this, and saved in the Jazz Copybook library as Parts.jzc: -
DEFINE Parts VSAM DATA(
Partnbr INTEGER KEY,
PartName CHAR(30) DKEY,
StandardPrice
DECIMAL(7,2))
DSNAME
'ibmuser.vsam.Parts';
Instead of PROCESS I wrote FOR JZ.IX = 1 TO 3; and within the FOR loop I wrote statements to create the data that I wanted for these three records. Here is the complete program: -
PROGRAM PartsCR BATCH;
COPY Parts;
FOR JZ.IX = 1 TO 3;
parts.partnbr =
JZ.IX * 2;
#361 E Assignment to a key field
Parts.partname =
'Part Name';
Parts.StandardPrice =
JZ.IX * 5;
WRITE Parts;
#378 W Batch WRITE used - you may need to edit the JCL
#348
PRINT (Parts.*) ;
END FOR;
A new VSAM
file must be created by IDCAMS before you can write to it (or do anything else
with it). The
easiest way to do this is to get Jazz to do it for you .
We can easily enhance our logic to select particular records with PROCESS file WHERE …, to use GET to retrieve matching records from other files and include fields from these records in our output, and to calculate data such as record counters and running accumulators. These are left as exercises for the reader.
In the previous chapter we used GET … WHERE to retrieve a record by key value: -
PROGRAM AAnExmpl BATCH;
COPY IN1;
COPY FR;
PROCESS IN1 WHERE (IN1.Region > 5) ORDER(IN1.Region, IN1.District, IN1.Name);
GET FR WHERE (FR.Region = IN1.Region);
PRINT(IN1.Region, Fr.Name, IN1.SalesThisMonth SUM, IN1.SalesYTD SUM)
BREAK(IN1.Region, IN1.District);
END PROCESS IN1;
We can use GET
to update records like this: -
GET FR WHERE (condition) UPDATE;
With UPDATE the GET statement normally becomes
like PROCESS, IF, FOR etc, starting a “GET block” that
requires END GET to
terminate. Imagine that FR contains a
field TotalSales, and we want to increment this by the value of
IN1.SalesThisMonth for the region. We
might write an update program like this: -
PROCESS IN1 WHERE (IN1.Region = 1 | IN1.Region = 6)
ORDER (IN1.Region BREAK, IN1.District BREAK, IN1.Name) INDEX JZ.JZ-INDEX;
GET FR WHERE (FR.Region = IN1.Region) UPDATE;
FR.SalesThisMonth
+= IN1.SalesThisMonth;
END GET FR UPDATE;
PRINT
(IN1.Region,FR.Name, IN1.District, IN1.Name, IN1.BillingCycle,
IN1.SalesThisMonth
SUM, IN1.SalesYTD SUM);
END PROCESS IN1;
The logic of GET… UPDATE/END GET is simple: at the GET a record is read, changes may be made to it
between GET and END GET, and then at the END the changed record is updated in the
file. The updating statements, FR.SalesThisMonth += IN1.SalesThisMonth in this case, logically occur between the GET and END, but in practice might be written in a ROUTINE.
Experienced programmers will have
spotted a problem with this program: it will work correctly, but it might be
very inefficient if there are many records for each IN1.Region: a record from
FR will be read and then updated for each of them. The input is in Region sequence: it would be better if we only read and updated FR when Region changes.
We can do this by adding ONCHANGE to the GET … UPDATE: -
PROCESS IN1 WHERE (IN1.Region = 1 | IN1.Region = 6)
ORDER (IN1.Region BREAK, IN1.District BREAK, IN1.Name) INDEX JZ.JZ-INDEX;
GET FR WHERE (FR.Region = IN1.Region) UPDATE ONCHANGE;
FR.SalesThisMonth
+= IN1.SalesThisMonth;
PRINT
(IN1.Region,FR.Name, IN1.District, IN1.Name, IN1.BillingCycle,
IN1.SalesThisMonth SUM, IN1.SalesYTD SUM);
END PROCESS IN1;
Now GET … UPDATE behaves like an input-only GET, reading an FR record only when Region changes.
We don’t need END GET to show where the record is updated: it is updated
before the next record is read, and at end-of-program. Let’s look at how this logic works: -
1.
PROCESS reads the input file IN1, sorting it by IN1.Region and
selecting only records for which IN1.Region is greater than 1 or 6.
2.
If
there is at least one record meeting the WHERE criteria, then statements within
the PROCESS/END are executed. For the first such record, GET FR reads
the appropriate record
3. The assignment statement adds IN1.SalesThisMonth to FR.SalesThisMonth. The operator += makes this statement the same as if we had written FR.SalesThisMonth = IN1.SalesThisMonth + FR.SalesThisMonth;
4. When we reach the GET statement for the second record, it may have the same or a different value of IN1.Region. If it has the same value then nothing happens and the program continues with the record that has already been read. This means that if there are two or more records for the same value then we continue to add the input data into the record, without any further I/O. However if the value has changed then the program will rewrite the previous record if it was found by the previous GET, or write a new record if the previous GET didn’t find a record and so created a new one. We will get the correct results whether or not we use ORDER, but our program will minimize I/O and so be much more efficient if we do.
5. When the program finishes it will check to see if there is a pending update, and write/rewrite the FR2 record if so.
Note that this program would work correctly if ORDER were omitted from the PROCESS statement, but without grouping the IN1 records by IN1.Region the GET statement might have to rewrite and read FR records with every IN1 record. Without ORDER the program may take much longer to run!
You can update a VSAM or SQL file by adding UPDATE to the PROCESS statement: -
PROGRAM PartsUP BATCH;
COPY Parts;
DEFINE W DATA(PreviousPrice LIKE Parts.standardprice);
PROCESS Parts UPDATE;
W.PreviousPrice
= Parts.StandardPrice;
Parts.standardPrice
+= 5;
PRINT
(Parts.*, W.PreviousPrice) ;
END PROCESS Parts;
As the PRINT shows, prices have been increased by 5: -
Printed at 25 Apr 2016,
00:02:16 Report1 Page 1
*--Partnbr---* *----------PartName----------*
StandardPrice PreviousPrice
2 Part Name 10.00 5.00
4 Part Name 15.00 10.00
6 Part Name 20.00 15.00
Of course the update logic might be more sophisticated than simply adding 5 to the price, but this remains a simple process provided that we are updating existing records in a VSAM file. This doesn’t create new records.
Jazz doesn’t yet support full
“Merge Updating”, where a transaction file is merged with a masterfile creating
a new masterfile that may contain new records, and several transactions may
update the same masterfile record. Enhancements to the PROCESS statement for this are
planned: refer to the description of the PROCESS
statement for details. However this
is not a priority for implementation: it can be implemented when requested.