More Batch Programming

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?”

 

More Batch Programming. 1

Writing Records. 1

Creating Files and zOS JCL. 5

Writing Data with NO Input 8

More Complex Logic. 12

Direct-Access Updating by Batch Programs. 12

Sequential Updating. 13

Sequential Merge Updating. 13

Writing Records

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: -

Creating Files and zOS JCL

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.

Physical Sequential Files

Change this to have DISP=(NEW,CATLG), and add a suitable SPACE parameter.  You may also need to give other parameters like VOL.

VSAM files

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.

SQL files (tables)

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.

Writing Data with NO Input

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 W VSAM file must be empty

    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 .

More Complex Logic

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.

Direct-Access Updating by Batch Programs

In the previous chapter we used GETWHERE 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 GETUPDATE/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 GETUPDATE: -

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 GETUPDATE 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!

Sequential Updating

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.

Sequential Merge Updating

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.