The GET Statement

The GET Statement 1

Statement Format 1

Statement Options. 2

WHERE.. 2

KEY (key [keyoption] [OR key [keyoption]]…) 2

Multiple keys (alternate indexes): using OR.. 3

Record not found. 4

Zero (or default) Key Values. 4

GET and Multiple Records. 5

FIRST. 6

ORDER.. 7

TS(nbr) 7

GET-related statements: END GET and GET processing. 7

Classical CICS Programs with Several GET Statements. 9

FIRST, AUTO, and MANUAL Options. 9

RESPOND.. 10

Updating Records with GET:  UPDATE | CREATE | REWRITE.. 11

When does updating happen?  Batch programs. 12

GET … UPDATE in CICS Programs. 12

Preventing Updates:  Update Cancel 13

Updating Key Values. 13

Updating Records Through an Alternate Index. 14

CICS and Record Locking. 14

CONTINUE.. 15

NBR.. 16

 

GET reads, and may update, a particular record. For example

*# Last Updated by robertb at 7/07/2013 10:06:19 a.m.

PROGRAM GRegion BATCH;

        *  Create VSAM Region file

DEFINE INFile TYPE(FB) DATA(

        Region PIC '999',

        Name CHAR(30),

        Fill CHAR(47));

DEFINE FRegion TYPE(VSAM) DATA(

        Region PIC '999' KEY,

        Name CHAR(30) Value('No Record found'),

        Fill CHAR(47));

PROCESS INFile ORDER(INFile.Region);

    GET FRegion WHERE (Fregion.Region = INFile.Region);

    PRINT (INFile.Region, INFile.Name, FRegion.name HEADING 'Name from VSAM');

END PROCESS infile;

 

FRegion is a reference file containing Region Names. The GET statement gets the record for which the value of FRegion.Region matches the value read from InFile.

Statement Format

In Batch programs

GET File-Name {WHERE (condition) | KEY(key [OR key]…)} [FIRST] [ORDER] [{UPDATE | REWRITE | CREATE} ONCHANGE] ENDGET

 

In CICS programs with VSAM or DB2  

            GET File-Name {WHERE (condition) | KEY(key [OR key]…)} [{SAVECOPY | SAVESUM} | UpdateOptions

[FIRST | {TS(nbr) [AUTO | MANUAL]}] [RESETFUNCTION]  [INTO Name] [ENDGET]

            UpdateOptions ::= {UPDATE | REWRITE | CREATE}  [{CHECKCOPY name | CHECKSUM name}]  CONTINUE

                        A Check option is required with UPDATE and REWRITE, but not with CREATE.

In CICS programs with TS (Temporary Storage) files

GET File-Name NBR recordnumber;

Statement Options

WHERE

WHERE(condition).  The condition must have the form

            field-in-file = value [& field2-in-file = value2]…

Thus whereas a condition in PROCESS …. WHERE(condition) is a general filter and can be any condition that can be written in an IF statement, the condition in a GET statement is more like a series of assignment statements to key fields in the file. The WHERE condition must be sufficient to specify a single record.

 

For VSAM: -

·         field-in-file must be a field defined with a KEY option (KEY, AKEY, or DKEY)

·         The comparison operator must be “=”.  GET FREGION WHERE (FREGION.REGION > 5) is therefore invalid.

·         The value may be a constant, or a reference to a field defined outside this file.

·         If there are several parts to the condition, they are linked with & (AND). You cannot use the Boolean operator | (OR).  All of the field-in-file names must be defined together within the same group.  Thus with a file defined like this: -

DEFINE RDFile TYPE(VSAM) DATA(

    KEYFIELDS GROUP KEY,

        REGION FIXED(3),
        DISTRICT
FIXED(3),

    ENDGROUP,

    NAME CHAR(30));

            a GET statement such as

            GET RDFile WHERE (RDFile.REGION = INPUT.REGION & RDFile.DISTRICT = INPUT.DISTRICT)

is valid.  However it is not valid if the fields REGION and DISTRICT are not defined within the same group, or if there are other fields also defined within this group.

 

For SQL, the WHERE condition is more flexible and may be anything that is valid within a SQL WHERE clause.  It is not checked by Jazz, except that an error is detected when the program runs if the GET attempts to return more than one record unless FIRST is specified.

KEY (key [keyoption] [OR key [keyoption]]…)

KEY indicates a field or group within the record to be used as the key for direct access.  For example, in this program the ACCEPT statement has already validated the screen field CICS4S.Keyfield and (if valid) moved it to Custf.Keyfield where it has the correct format to be used as the key for Custf.  It seems pointless to write

 GET Custf WHERE(Custf.Keyfield= Custf.Keyfield);

instead we use KEY: -

ACCEPT (CICS4S.Function, CICS4S.Keyfield);

CASE (CICS4C.Function);

    WHEN (Enquiry);

        GET Custf KEY(Custf.Keyfield) SAVECOPY CICS4C.SAVE;

        

The field/group named must be defined within the record.   Groups are given as generic references, e.g. Custf.KeyGroup.*

Multiple keys (alternate indexes): using OR

A record may be defined with alternate indexes using DKEY and UKEY options. For example, here is the definition of CustF.  You can look up records by Account or by Name: -

*# Last Updated by IBMUSER at 29/11/2014 5:57:18 p.m.

COPY Types;

DEFINE CustF TYPE(VSAM) DATA( 

    Account PIC '999999' HEADING 'Account Number' KEY,

    Ukey GROUP,   [later will have attribute UKEY.  For now, group keys not implemented

        Subgroup GROUP,        

            Region DECIMAL(3),       

            District DECIMAL(3),

            END GROUP,   

        Name CHAR(15) NOTNULL DKEY 'ibmuser.vsam.custf1' ,

        END GROUP,          

    SalesThisMonth MONEY(7,2),   

    SalesYTD MONEY(7,2),         

    Billingcycle LIKE types.month,

    DateCommenced CHAR(10))

    DSNAME 'ibmuser.vsam.custf';

 

Thus to read CustF with a particular Account value you’d write either

            GET CustF WHERE(Custf.Account = value);

or         GET CustF KEY(Custf.Account);

while to read CustF with a particular Name value you’d write

            GET CustF WHERE(Custf.Name = value);

or         GET CustF KEY(Custf.Name);

 

Where there are several possible keys you can use KEY(key1 OR key2), like this: -

          GET CustF KEY(Custf.Account OR Custf.Name);

 

OR uses the first value found that has a non-default value (normally blank or zero, unless there’s a VALUE clause).  Thus the GET statement above is equivalent to: -

IF Custf.Account <> ' ' THEN;

            GET CustF KEY(Custf.Account);

ELSEIF Custf.Name <> ' ' THEN

            GET CustF KEY(Custf.Name);

ELSE;

            INITIALIZE Custf;

            Custf.$Found = ‘N’;

END IF;

 

You’ve probably written a preceding ACCEPT statement that also uses OR: for example, here we are asking users to enter either an account number or customer name in the screen, and then looking up the file based on whichever value they enter: -

ACCEPT (CICS1S.Account OR CICS1S.Name);

GET custf KEY(CustF.Account OR CustF.Name);

 

There may be many possible keys: -

          GET CustF KEY(Key1 OR Key2 OR Key3 …);

Record not found

When GET attempts to read a record but there is no record with the requested key value then Jazz creates a record for you in memory with the key value sought, and other field values set to their default values. For example, in my test file there was no value for Region=9, so when

GET FRegion WHERE (Fregion.Region = InFile.Region);

is executed with InFile.Region=9 a dummy record is created with FRegion.Region = 9 and FRegion.Name = Blank. This allows the PRINT statement to work normally.

 

If you want to test for “record not found” use the special value $Found: -

IF FRegion.$Found THEN ….

 

If you want to report that there was no FRegion record then an easy way of doing this is to set a suitable Value clause: -

DEFINE FRegion TYPE(VSAM) DATA(

    REGION PIC ‘999’,

    NAME CHAR(30) VALUENot Recorded’);

This avoids the need for code such as: -

GET

IF FRegion.$Found THEN

    FRegion.Name = ‘Not Recorded’;

END IF;

PRINT ….

Zero (or default) Key Values

Avoid situations where there could be a real record with the default key value!  For example with FRegion defined

DEFINE FRegion TYPE(VSAM) DATA(

        Region PIC '999' KEY,

        Name CHAR(30) Value('No Record found'),

        Fill CHAR(47));

the default value of Fregion.Region is zero.  If you have a real record on the file that has Fregion.Region with this value then your GET may not retrieve this real value, but instead will use the initialised record.  For example, a batch program with

PROCESS in1 ORDER(in1.region, IN1.District);

    GET FRegion WHERE Fregion.region = in1.region;

   

END PROCESS in1;

will not read the real record for Fregion.region = 0 but instead your program will think that the record has already been read and so the value of Fregion.region will be 'No Record found' no matter what value is in the file.  This is because batch GET logic tests to see if the record is already in memory to avoid unnecessary I/O.  In this particular case you could have avoided the error by processing data in reverse order, but you can’t avoid the error when you use OR: -

    GET FRegion KEY Fregion.region OR  Fregion.Name;

GET and Multiple Records

GET is designed to GET a single record. The archetypal situation: you look up a record using its primary key: -

CustF.Account = 23;

GET custf KEY(CustF.Account);

You either return a record or you don’t  If there is a record with Account = 23 it will be returned.  If there isn’t Jazz will initialise the record area: Custf.Account will be set to 23, everything else will be set to default values – normally zeros and blanks.

 

What if there might be several possible records?  For example, with Custf.Name defined like this there might be several records with name 'BARNES': -

DEFINE CustF TYPE(VSAM) DATA( 

   

    Name CHAR(15) REQUIRED DKEY 'ibmuser.vsam.custf1' ,         

       

ACCEPT (CICS1S.Name);

GET custf KEY(CustF.Name);

 

GET can only return one record from GET File KEY(File.KeyField); when

  • File.KeyField is defined with property KEY or UKEY, and its format is not CHAR, or
  • File.KeyField is defined with property KEY or UKEY, its format IS CHAR, and also property FULL is specified. For example,
                Name CHAR(15) KEY FULL,

GET can return one of several records from GET File KEY(File.KeyField); in other cases, i.e.

  • File.KeyField is defined with property DKEY
  • File.KeyField has format CHAR and you have not specified property FULL.

 

When CHAR fields are used as keys then one of the key options “GENERIC, WILDCARD ‘character’, and FULL may be given.  The default is GENERIC.  These options may be given in either the DEFINE statement or the GET statement, with the option from the GET statement taking precedence. For example, with Name defined as above: -

            Name CHAR(15) KEY FULL,

you might write a GET statement like this: -

GET custf KEY(CustF.Name GENERIC);

in which case the GET statement will use GENERIC rules, using only the length of the value given.

 

The GET statement can only return one record, and in a batch program this is all you get. With a CICS program Jazz will generate logic that returns the first record, but enables PF10 as “Previous” and PF11 as Next when relevant.  It is not clear which record will be the first.

 

With the default of GENERIC the GET searches for records matching the search value for the length entered.  Thus if you entered value “Apthorpe” (using my test data as CustF) the system could return several records: -

            APTHORPE, Alice

            APTHORPE, Ben

            APTHORPE, John

           

            APTHORPE, WILLIA

 

The GET statement initially returns the first (Alice), but then with PF11 you can read the next.  Once you’ve moved beyond the first record PF10 is also enabled, and you can go forwards and backwards through the list: -

 

With WILDCARD ‘x’ you specify that a special character is used to delimit the string.  This is typically “*” or “%”, although you can use any character that doesn’t appear in the data.  Thus if you wrote

          GET custf KEY(CustF.Name WILDCARD '*’);

the user would enter “Apthorpe*” to return the same records as above.

 

By writing: -

GET custf KEY(CustF.Name FULL);

you force the program to use the full length of the field, so that you’ll only find records with key value

            APTHORPEbbbbbbb

(“b” represents a blank)

 

For key fields that do not have CHAR format, like Custf.Account, you cannot use these options, and effectively the statement uses the FULL option.

FIRST

In CICS programs: handle only the first record, do not provide next/previous paging.

 

In Batch programs:  generate code that will handle multiple records without error, but return only the first to your program.   This is the default, but specifying FIRST explicitly suppresses some error messages when analysis of SQL definitions would complain about ambiguity.

ORDER

This option is only valid for SQL definitions.   If GET can return many records then which is returned first is undefined.  ORDER specifies an order, defining which record you’ll get with GET …. FIRST.  Use this option sparingly: it may create a lot of processing.

TS(nbr)

It is highly unlikely that you will write this option yourself, more likely Jazz has inserted it.  It only applies to classical CICS programs, not to batch or Web Service[1] programs. 

 

Suppose you write this in a CICS program:-

GET custf KEY(CustF.Name);

 

There may be zero, 1, or a large number of records matching this key.  Your program needs a way of keeping track of these records so that you can easily go forward or back through them with PF11 and PF10.  Jazz does this by creating an in-memory TS file containing the record keys, and it will add a definition of this file to your program, and this option to the GET statement.  Thus the statement above becomes: -

DEFINE TS1 TYPE(TS) DATA(

    Account LIKE CustF.Account);

GET custf KEY(CustF.Name) TS(1);

This is all automatic, and Jazz looks after creating this file, writing and reading records to it, using it to read particular records from Custf, and creating the visual clues like

           

at the bottom of your screen.

GET-related statements: END GET and GET processing.

When a GET statement might return several records you may want to group some of the following statements to tell Jazz that they are to be executed with it.  You do this by writing one or more statements after the GET, followed by END GET.  For example: -

        GET custf KEY(CustF.Account OR CustF.Name) ENDGET;

            Statements to be executed after every Read from custf

        END GET custf;

You have to add an option to the GET statement to let Jazz know that the following statements form a GET group.  You can add the option ENDGET, as above, but ENDGET is implied by any of the update options (UPDATE, CREATE, REWRITE), INTO, and TS.  The following statements, to END GET, are executed whenever GET attempts to retrieve a record.  If no record was found, then they will be executed with an initialised record which has the key value requested, and all other fields set to their default values.

 

For example a GET statement with TS may return multiple records, implying the possibility of Next/Previous logic invoked by PF11/PF10.  When you get a new record your program may need to get other data as well.  For example, here a program reads a CustF record and also the related Orders records.  Like the GET statement the PROCESS statement needs to use a TS file to allow for the possibility of there being more 2nd-level records than can be put on the screen at once: -

CASE (CICS3C.Function);

    WHEN (Enquiry);

        ACCEPT (CICS3S.Account OR CICS3S.Name);

        DEFINE TS1 TS DATA(

            Account LIKE CustF.Account);

        GET custf KEY(CustF.Account OR CustF.Name) SAVECOPY CICS3C.SAVE RESETFUNCTION TS(1);

        #373 I GET statement returns one record at a time for Name

        DEFINE TS2 TS DATA(

            OrdNbr LIKE Orders.OrdNbr);

        PROCESS Orders WHERE (orders.ordcustid = custf.account) TS(2) INDEX;

            #082 W Default name 'JZ-INDEX' used for INDEX option

            CICS3S.OrdNbr(JZ.JZ-Index) = Orders.OrdNbr;

            CICS3S.Orddate(JZ.JZ-Index) = Orders.ordDate;

            CICS3S.Ordpart(JZ.JZ-Index) = Orders.ordpart;

            CICS3S.OrdQty(JZ.JZ-Index) = Orders.OrdQty;

            CICS3S.OrdDiscount(JZ.JZ-Index) = Orders.OrdDiscount;

            CICS3S.OrdStatus(JZ.JZ-Index) = Orders.OrdStatus;

        END PROCESS orders;

 

Obviously the Orders records are related to the CustF record, and if PF10/11 gets another CustF record then the Orders records have to be refreshed also.  The above logic is incorrect: while it would work when records are initially read, it would not work when PF10/11 is used to move backwards and forwards through the CustF records.  We need a way of telling Jazz that the statements following the GET are “part of the GET process”, so that it can ensure that whenever we get a record, including paging, the related statements are executed correctly.   We do this by writing them within GETEND GET, like this: -

        GET custf KEY(CustF.Account OR CustF.Name) SAVECOPY CICS3C.SAVE RESETFUNCTION TS(1);

            #373 I GET statement returns one record at a time for Name

            DEFINE TS2 TYPE(TS) DATA(

                OrdNbr LIKE Orders.OrdNbr);

            PROCESS Orders WHERE (orders.ordcustid = custf.account) TS(2) INDEX;

                #082 W Default name 'JZ-INDEX' used for INDEX option

                CICS3S.OrdNbr(JZ.JZ-Index) = Orders.OrdNbr;

                CICS3S.Orddate(JZ.JZ-Index) = Orders.ordDate;

                CICS3S.Ordpart(JZ.JZ-Index) = Orders.ordpart;

                CICS3S.OrdQty(JZ.JZ-Index) = Orders.OrdQty;

                CICS3S.OrdDiscount(JZ.JZ-Index) = Orders.OrdDiscount;

                CICS3S.OrdStatus(JZ.JZ-Index) = Orders.OrdStatus;

            END PROCESS orders;

        END GET custf;

 

You may write logic following END GET which will not be re-executed by PF10/11.

 

Because of the possibility of related logic, END GET is required if TS etc is present even when there are no following statements.  Future Jazz releases should eliminate this requirement: Jazz should know that END GET is unnecessary in situations like: -

        GET custf KEY(CustF.Account OR CustF.Name) RESETFUNCTION TS(1);

        END GET custf;

Classical CICS Programs with Several GET Statements

Your program logic might use several GET statements.  If only one of these could potentially return several records then programming is straightforward: -

            GET FRegion KEY (Fregion.Region);

GET FDist KEY (FDist.District);

GET Custf KEY(CustF.Account OR CustF.Name) TS(1);

    #373 I GET statement returns one record at a time for Name

   

END GET Custf;

 

However what if one of these other GET statements were to handle multiple records also?  Jazz will give each multi-record GET its own TS file, and require an END GET statement to close off the GET logic. For example: -

GET FRegion KEY (Fregion.Region);

GET FDist KEY (FDist.District) TS(1);

    #373 I GET statement returns one record at a time for District

   

END GET FDist;

GET Custf KEY(CustF.Account OR CustF.Name) TS(2);

    #373 I GET statement returns one record at a time for Name

   

END GET Custf;

What about the automatic PF Key handling?  Should PF10/11 control FDist or CustF?  It can hardly control both!  You’ll need to indicate which is controlled automatically: you do this by choosing one of the options FIRST, AUTO, or MANUAL.

FIRST, AUTO, and MANUAL Options

GET …. FIRST specifies that, even if there might be many possible records satisfying the GET key criteria only the first will be returned.  Temporary Storage files are not used, there is no mechanism for getting other records, and no need for END GET.  As noted above, it can be uncertain which of several records is “the first”.  The code above becomes: -

GET FRegion KEY (Fregion.Region);

GET FDist KEY (FDist.District) FIRST;

GET Custf KEY(CustF.Account OR CustF.Name) TS(1);

    #373 I GET statement returns one record at a time for Name

   

END GET Custf;

 

When there are several GET statements using TS files then only one may use the automatic PF10/11 logic.  You may designate which by writing AUTO with this GET, and MANUAL with the others: -

GET FRegion KEY (Fregion.Region);

GET FDist KEY (FDist.District) TS(1) MANUAL;

    #373 I GET statement returns one record at a time for District

   

END GET FDist;

GET Custf KEY(CustF.Account OR CustF.Name) TS(2) AUTO;

    #373 I GET statement returns one record at a time for Name

   

END GET Custf;

By default Jazz will apply AUTO to the first GET statement with TS, and MANUAL to all the others.

 

To handle GETMANUAL, since you can’t use the automatic PF10/11 logic you have to write the equivalent yourself, either using free PF keys with HANDLE, or using data values such as further Function values. In your logic you will read the TS file explicitly using GET, and then use the retrieved key to GET the data record.

RESPOND                                                                                                             

RESPOND is designed for situations like a web service provider that might return several records.  As noted above,

GET is designed to GET a single record: -

CustF.Account = 23;

GET custf KEY(CustF.Account);

There either exists a record with this Account value, in which case it will be retrieved and Custf.* will hold the data from this record, or else it doesn’t exist and Custf.* will have initialised values.

 

However GET might return several records.  For example, with

GET custf KEY(CustF.Account OR CustF.Name);

retrieval using CustF.Name could return any number of records. In a classical CICS program Jazz deals with this by reading the keys of all qualifying records into a temporary storage file, returning the first, and implementing logic so that if the user clicks PF11 then the next record is returned, while PF10 returns the previous.  The classical CICS program will only deal with one record at a time, and so every time you want to move forward or backward through the file you go back to the program and read the next (or previous) record.

 

Imagine a program whose job it is to retrieve CustF records and provide their data via a SOAP message to a requesting program. Unlike a 3270 screen, a web service message is not restricted to 1920 characters (24 lines * 80 characters), and so instead of returning one record at a time, requiring the requesting program to keep track of record position and read records as necessary, it might be much more convenient to simply return all the possible records at once, allowing the requesting program to populate a combo box, display them as a table, or do whatever it wants with the set of records. To achieve this, add RESPOND record.* to the GET statement, and/or the END GET statement.  For example

GET custf KEY(CustF.Account OR CustF.Name) RESPOND OWSPG1;

END GET CustF RESPOND OWSPG2;

or

GET custf KEY(CustF.Account OR CustF.Name);

END GET CustF RESPOND OWSPG1;

If the GET statement uses RESPOND then there MUST be an END GET file RESPOND statement. 

 

The response is prepared at the END statement, and therefore will include any changes to the record that have been made between GET and END GET.

 

RESPOND is designed to be used with the logic and data structures that are generated from New/Logic/WebService.  Refer to JazzUGSOA4.htm to see how to generate such programs. If you create the logic and data manually, then you must ensure that the data structures are correct. The record named by RESPOND must be a record or a group with a structure that follows these rules: -

·         There must be two SMALLINT control variables, named JZ-filename-ReadTo and JZ-filename-NbrReturned

·         Then an array of records: minimum dimension 1, maximum 1000. 

·         Each record may be a complete copy of the database record or a selection of fields from the record. 

·         The fields must include the record’s primary key. 

·         Any TINYINT fields in the corresponding record should be defined as SMALLINT: this is to avoid a problem where some hexadecimal values, such as X‘03’, can cause SOAP message errors.

 

Here is a valid example, created automatically by Jazz: -

DEFINE OWSPG1 SERVICE DATA([Output message

    ERROR VARCHAR(80) [make longer if necessary],

    ViewState LIKE IWSPG1.ViewState,

    JZ-custf-ReadTo SMALLINT VALUE 0,

    JZ-custf-NbrReturned SMALLINT VALUE 0,

    custf (3) GROUP,

        Account LIKE custf.Account,

        Region LIKE custf.Region,

        District LIKE custf.District,

        Name LIKE custf.Name,

        SalesThisMonth LIKE custf.SalesThisMonth,

        SalesYTD LIKE custf.SalesYTD,

        Billingcycle SMALLINT,

        DateCommenced LIKE custf.DateCommenced,

 

GETRESPOND will read as many of the qualifying records as possible into the array of records, and set the control variables.  For example: -

If there are three qualifying records, then

·         JZ-custf-ReadTo will be set to 3

·         JZ-custf-NbrReturned will be set to 3

·         The first three rows of OWSPG1.custf will contain values from these three records.

 

If there are more records than can be returned, say 5, then the maximum number, 3, are returned.  The input message contains a Skip field named (in this case) JZ-custf-Skip and client programs can use this to implement paging logic.  For example if you set JZ-custf-Skip to 3 then records 4 and 5 will be returned.  You can test if there are more records by testing that Skip + Returned < ReadTo.  For example, in the Visual Basic program used to invoke a demonstration invoking program WSPG1 that could return up to 3 records: -

    If Iwspg1.jz_custf_Skip + OWspg1.jz_custf_NbrReturned < OWspg1.ViewState.jz_custf_ReadTo Then

            '   There are more records

    Else    '   No more records

    End If

Iwspg1 is the service’s input message (the invoking program’s request message), and OWspg1 is the service’s output message (the response).

Updating Records with GET:  UPDATE | CREATE | REWRITE

By adding UPDATE to the GET statement you can retrieve a record from VSAM or SQL, make changes to it, and then update the record in the database.  For example, this is a simple batch update program that will update FRegion.Name values from an input file.

PROCESS InFile ….

    GET FRegion where (FRegion.Region = InFile.Region) UPDATE ONCHANGE;

    FRegion.Name = InFile.Name;

END PROCESS InFile;

 

Similarly, this is part of a CICS program updating the CustF file: -

GET custf WHERE(CustF.Account=CICS2C.Save.Account) UPDATE CHECKCOPY(CICS2C.SAVE);

    ACCEPT (CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,

                       CICS2S.Billingcycle,CICS2S.DateCommenced);

END GET custf UPDATE;

 

GET will create an initialised record (mostly blanks and zeros) in memory when it doesn’t find the record that it’s looking for.  GET file UPDATE will add this record to the file, so that the program above can create new FRegion records from InFile. 

 

GETUPDATE,  GETREWRITE and GETCREATE all update the file, but: -

·         GETREWRITE must find an existing record to update:  when the GET attempts to read a record the program will be aborted (with a message) if no record with the requested key value is found.

·         GETCREATE is the reverse situation, it must NOT find an existing record to update.  GET attempts to read a record (and will lock the key so that other users can’t create another record with the same key), but the program will be aborted (with a message) if a record with the requested key value IS found.

·         GETUPDATE is the most flexible, accepting both Found and Not Found without complaint.

When does updating happen?  Batch programs

In batch programs the GET UPDATE will be within a loop: -

PROCESS InFile ….

    GET FRegion where (FRegion.Region = InFile.Region) UPDATE ONCHANGE;

    FRegion.Name = InFile.Name;

END PROCESS InFile;

 

Updating occurs before the GET attempts to read the next record, and also when the program terminates (so that the last record read is updated). If the previous GET created a new record in memory, then this new record is inserted into the database. Thus several records may update the same GET record, but I/O is minimized with an appropriate input sequence. $Found remains set true or false from the last actual I/O.

 

Without ONCHANGE the GET UPDATE will require an END GET, and updating will take place when the END is reached.  Each iteration of the PROCESS loop will cause a record to be read, and then updated.

GET … UPDATE in CICS Programs

Updating in a CICS program doesn’t rely on a loop, but instead we write GET …. END GET and the updating takes place at the END statement: -

GET custf WHERE(CustF.Account=CICS2C.Save.Account) UPDATE …;

    Change the values in Custf fields

END GET custf UPDATE;

Changes made between GET and END GET, including changes made as a result of PERFORM statements written between GET/END, will be rewritten to the file.  If the GET didn’t find a record it will have initialised the record fields: this new record is added to the file by ENDUPDATE; 

 

The END GET must use the same update keyword as the GET statement:   GETUPDATE is matched with ENDGET UPDATE,  GETCREATE with ENDGET CREATE, and so on.  You can also follow GETUPDATE with ENDGET REWRITE

Preventing Updates:  Update Cancel

What if you’ve set up an update with GETUPDATE but in some conditions you don’t want to update the record?  You can turn off updating with UPDATE filename CANCEL.   For example

GET Vfile WHERE (Vfile.charfield = r2.charfield) UPDATE;

IF Vfile.Region = 5;

    UPDATE Vfile CANCEL;

END IF;

 

UPDATECANCEL does not undo an update.  This would have no effect: -

GET custf WHERE(CustF.Account=CICS2C.Save.Account) UPDATE CHECKCOPY(CICS2C.SAVE);

    ACCEPT (CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,

                       CICS2S.Billingcycle,CICS2S.DateCommenced);

END GET custf UPDATE;

IF CICS2S.Region = 3;

    UPDATE Custf CANCEL;   <=  This is completely pointless

END IF;

 

Note that you always write UPDATECANCEL whatever the update type: you don’t write REWRITECANCEL or CREATECANCEL.

Updating Key Values

When updating a record you may change any of its fields, except its KEY field.   Thus

GET custf WHERE(CustF.Account=CICS2C.Save.Account) UPDATE CHECKCOPY(CICS2C.SAVE);

    ACCEPT (CICS2S.Account, … );

END GET custf UPDATE;

is incorrect, as it can change the value of the record’s key.  This also applies to attempts to change the key through a redefinition: it is just as invalid to attempt to change the key through field Custf.AccountAsChar: -

DEFINE CustF VSAM DATA( 

    Account PIC '999999' HEADING 'Account Number' KEY,   

    AccountAsChar CHAR(6) REDEFINES CustF.Account,

 

If you want to change the record key, you must delete the record with the old key, and create a new record with the new key value.  However there is no problem with alternate index keys: you may change the values of DKEY and UKEY fields just like any other fields.

Updating Records Through an Alternate Index

You can define fields as UKEY or DKEY: for VSAM this uses an alternate index to look up the record.  UKEY means “Unique Key”, and DKEY means “Duplicate key”.  You’d use UKEY for situations like recording a person’s social security number, while you’d use DKEY for their name: -

            DEFINE CustF TYPE(VSAM) DATA(

                Custid PIC '999999' KEY,

                SocialSecurity CHAR(15) UKEY,

                Name CHAR(30) DKEY,

                other data …

 

You can GET records based on these UKEY or DKEY values: -

            GET CustF WHERE(Custf.SocialSecurity = value);

or         GET CustF WHERE(Name = value);

 

When updating records through an alternate index you can only update existing records, you cannot create new ones. Thus you cannot use the UPDATE option, instead you use REWRITE.   If there can be several records for a key value, then only the first will be retrieved and updated unless your CICS program provides a Next function (PF11) to navigate through the possible records.

 

If you use GET file KEY(key1 OR key2) UPDATE then there will be a message and the program will use REWRITE even when the record is retrieved by its primary key.

CICS and Record Locking

In an on-line environment where many users may be updating the database at the same time it is possible that between the time that GET retrieves a record and the time that following UPDATE is to be done that another user may have updated the record. To prevent this we could lock the database when we first read the record, releasing the lock when we update it. However if we hold a lock for too long the database could quickly become unusable if there are many other users also holding locks.

 

Instead, when the GET first reads the record we don’t lock the database, but we either save a copy of the record in its original form, or we save a CheckSum hash.  Later, when the UPDATE is to be done the program re-reads the record, this time locking the database. The program then compares the re-read record with the saved copy (or Checksum) : if they’re the same then our update is done and the lock removed. However if they differ the update is rejected. Either way the lock is held only briefly.  In the event of failure the user will have to re-apply their updates, but database integrity is preserved: the extremely rare occurrence of having to re-do an update is much less of an inconvenience than having the database freeze up from time to time due to large numbers of long-duration locks.

 

In a classical CICS program the initial GET uses SAVECOPY to save a copy of the record in COMMAREA. The record is not locked.  Later the GET UPDATE uses CHECKCOPY to compare the saved copy with the latest record.  For example: -

 

CASE (CICS2C.Function);

    WHEN (Enquiry);

        GET Custf SAVECOPY (CICS2C.SAVE);

       

    WHEN (Update, Add);

        GET Custf UPDATE CHECKCOPY (CICS2C.SAVE);

        #221 W Account used as key: it must already have the value sought.

        ACCEPT (CICS2S.Region,CICS2S.District,CICS2S.Name,CICS2S.SalesThisMonth,CICS2S.SalesYTD,CICS2S.Billingcycle,CICS2S.DateCommenced);

        UPDATE Custf;

        CICS2S.error = 'Update/Addition successful. Enter next account number';

        CICS2C.Function = Enquiry;

    WHEN (Delete);

       

END CASE;

 

For the update GET Custf UPDATE CHECKCOPY (CICS2C.SAVE); reads the record for update (locking it), checking it against the saved copy from the previous enquiry to ensure that no other users have updated it.  The program will exit with a message if there is a problem.   Then ACCEPT then gets information from the input screen, validates it, and updates the record in memory.  Again, there will be messages and the program will exit if there are problems. In the absence of errors the program will reach the UPDATE statement, where the record is actually updated.  Note that in a CICS program, unlike a BATCH program, an explicit UPDATE statement must be written.  With the different structure of an on line program Jazz can not be completely certain where the UPDATE should go, so it is left to the programmer.

 

In a web service we can’t use COMMAREA in this way as web services are completely stateless.  Instead information has to be passed between the provider and requester in the messages.  SAVESUM and CHECKSUM are used instead of SAVECOPY and CHECKCOPY.  Instead of passing a copy of the record to/from the client an encrypted record digest is passed.  Any change to this digest by the client, or any change to the record by another user or process, means that when  GET Custf UPDATE CHECKSUM is executed a difference the recalculated record digest will differ from the returned digest, causing the update to fail.

CONTINUE

When a GETUPDATE CHECKSUM (or CHECKCOPY) detects that the record has changed the normal action is for the program to produce a message and immediately terminate.  If previous statements like ACCEPT have used CONTINUE then they may have produced messages: these prior messages will be overridden by the message produced by GET.

 

You can specify that the program will continue with following Jazz statements by adding CONTINUE to the GET statement.  Thus without CONTINUE:-

 GET custf WHERE(CustF.Account=IWSPG2.Account) UPDATE CHECKSUM IWSPG2.CheckSum;

        ACCEPT (IWSPG2.Region,IWSPG2.District,IWSPG2.Name,IWSPG2.SalesThisMonth,

                   IWSPG2.SalesYTD,IWSPG2.Billingcycle,IWSPG2.DateCommenced)

            TO custf MESSAGE OWSPG2.ERROR;

            #447 I IWSPG2.Region,IWSPG2.District,IWSPG2.SalesThisMonth,IWSPG2.SalesYTD,IWSPG2.Billingcycle are already numbers

  END GET custf UPDATE RESPOND OWSPG2;

This will immediately terminate with a message if the re-calculated CHECKSUM is not the same as that in the input record.  The ACCEPT is not executed, and the response message will show initialised record fields, not the CustF record read from the file.  However, with

GET custf WHERE(CustF.Account=IWSPG2.Account) UPDATE CHECKSUM IWSPG2.CheckSum CONTINUE;

        ACCEPT (IWSPG2.Region,IWSPG2.District,IWSPG2.Name,IWSPG2.SalesThisMonth,

                   IWSPG2.SalesYTD,IWSPG2.Billingcycle,IWSPG2.DateCommenced)

            TO custf MESSAGE OWSPG2.ERROR;

            #447 I IWSPG2.Region,IWSPG2.District,IWSPG2.SalesThisMonth,IWSPG2.SalesYTD,IWSPG2.Billingcycle are already numbers

  END GET custf UPDATE RESPOND OWSPG2;

execution continues with the ACCEPT statement, and the response message will show the “updated” record: values read from CustF modified by the ACCEPT statement.  However the file update is not performed: it’s as if an UPDATE CustF CANCEL; has been executed.

NBR

TS records can be retrieved by number. Thus if you have defined a TS file like this: -

            DEFINE TS TYPE(TS) DATA(

                RECORD LIKE Orders.*)

and you have written 20 records to it with

            PROCESS ORDERS WHERE

                TS.Record.* = Orders.*;

                WRITE TS;

            END PROCESS;

then you can read records by number using NBR, or simply by enclosing the number in brackets. For example, to read the 17th record write

            GET TS (17);

or

            IX = 17;   [IX should be a SMALLINT field

            GET TS (IX);

           

As with GET WHERE, if the record number is too large or too small the GET returns a NOT FOUND condition and the record area is initialised.



[1] Strictly speaking a TS file CAN be used in a web service, but it can’t be held from one request/response to the next, so in web service programs Jazz doesn’t use TS files automatically, and it is very unlikely that you’ll do so yourself.