Natural Tips & Techniques


This page is still in its infancy, but I plan to publish suggestions, code samples, even utilities, on a regular and frequent basis (regular and frequent being relative terms).

If you have any comments on the content or layout of this page please let me know.



 
Tip Date Published Description
9
02/28/1998
Natural source comparisons
8
11/05/1997
Monthly date calculations
7
11/05/1997
Dynamic report distribution
6 07/14/1997 Natural batch restartability
5 07/07/1997 Sequential match
4 07/07/1997 READ syntax
3 07/05/1997 Replacing certain AT BREAKs with a SORT
2 06/30/1997 Continuation Symbol 
Here are some uses you may not have thought of.
1 06/10/1997 Initialization of an SPDE
 
Tips are listed chronologically, in reverse to let you find the more recent ones quickly.

Return to Ralph G. Zbrog's Home page.


Tip 9    02/28/1998    Compare Natural source

There is no need to purchase additional software products to compare two Natural source members if you have MVS/TSO.  Just download ZZUTILs.  Then use the ZPUNCH utility to extract Natural source to a flat file, and IBM's SuperC comparison utility to tell you what the differences are.

ZZCOMPR is a Natural program which submits a batch "compare" job from on-line via SAG's NATRJE facility.  If you need to submit the jobs manually from TSO, extract the embedded JCL from ZZCOMPR and customize it as necessary.
 

Top of Tips & TechniquesPrevious tip.  Top of this tip.

Return to Ralph G. Zbrog's home page.


 Tip 8    11/04/1997    Monthly date calculations
 
Often we are called upon to compute dates which are one, two, and three months into the past, for aging report purposes.  Sometimes we need to know how many individuals in a file will reach their twenty-first birthday within the next six months.  In these and countless other situations, date calculations are being made based on a number of months.  For a quick estimate, we could multiply the number of months by thirty and then add or subtract this number from a D-format value.  For a more accurate computation, use ZZMONTH.

ZZMONTH is a Natural subprogram.  Pass it a date and a number of months and it returns a new date and a date range.  The new date will be in the future or past depending on whether the number of months is positive or negative, respectively.  The date range will be the first day of the month and last day of the month with respect to the new date.

For example
             CALLNAT 'ZZMONTH' #GIVEN
                               #MONTHS
                               #EXACT
                               #FROM
                               #THRU

If #GIVEN contains D'11/04/1997' and #MONTHS contains 3, upon return #EXACT will contain D'02/04/1998', #FROM will contain D'02/01/1998', and #THRU will contain D'02/28/1998'.  If #GIVEN contains D'11/04/1997' and #MONTHS contains -3, upon return #EXACT will contain D'08/04/1998', #FROM will contain D'08/01/1998', and #THRU will contain D'08/31/1998'.

The source for ZZMONTH includes an on-line mainline to help you test the subprogram.  Try setting #GIVEN to D'11/30/1995' and #MONTHS to 3.  Then change #GIVEN to D'11/30/1997' or D'02/28/1997'.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 7    11/04/1997    Report Distribution
 
Say you have a report which has a page break by Region Code.  Each region's pages should be printed on that region's printer.  In addition the head office wants a complete set of the regional report.  Prior to the DEFINE PRINTER statement it was common to write one program for head office and one for each region, or to break the data by region into subset datasets, reporting each dataset individually.

With the DEFINE PRINTER statement you need pass the data only once.  Use WRITE(xx) for the head office report (invariably I use WRITE(01) for this guy); you don't need a DEFINE PRINTER(xx).  Use WRITE(yy) for the regional reports (WRITE(02) seems pretty logical to me).  Create a subroutine to determine dynamically to which printer to route the current regional report.  Perform the routine at start of data (don't assume the first data record is for the first region) and at break of region.

        RESET *PAGE-NUMBER(yy)
        DECIDE ON FIRST #REGION
            VALUE 'a1'
                DEFINE PRINTER(yy) OUTPUT 'CMPRT02'
            VALUE 'b2'
                DEFINE PRINTER(yy) OUTPUT 'CMPRT03'
            ...

In your JCL, CMPRT02 and CMPRT03, etc, will point to specific regional printers.

You will want the WRITE(yy) TITLE statement to contain the word REGIONAL while the WRITE(xx) TITLE statement reflects its broader scope.  There is no need for a NEWPAGE(yy) statement.  Execution of the DEFINE PRINTER implies a new page.

The only difference in the detail line WRITE statements is the printer designation.  The field lists are identical.

Of course you still have a problem if you need more than a total of 31 report destinations.  That's Natural's limit.

You can see sample code and JCL here.

I would like to thank Ray Bergen for this tip.  Although I knew it was possible to do this in theory, Ray was the one who came up with the right combination of PERFORM, DECIDE, and OUTPUT specifications.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 6    07/14/1997    Natural Batch Restartability
 
ZZRESTRT is a source module which demonstrates how to code a restartable Natural program.  It is only a stub, requiring significant customization.  Optional code is identified with comments.

ZZRESTRT includes the following features:

Note that ZZRESTRT does not produce a formatted report.  This is because all reports would be fragmented under restart conditions.  Rather than present a user with a production report in two or more discontiguous portions, all report data are written to a work file.  Following the update step the work data set is sorted and reported, producing a single, contiguous report.

When a program ABENDs, all updates which have not been committed are backed out automatically, but all work records remain on the sequential output data set.  When the program is restarted, the backed out transactions are re-processed and duplicate work records are created (in the JCL the new records are appended to the original data set).  When the data set is sorted for reporting, as suggested above, the SEQuence field is used to eliminate the duplicates.  In DFSORT and SYNCSORT this is done by including the SEQuence field in the low order of the sort and including SUM FIELDS=NONE.

Also note the placement of the final WRITE WORK and summary audit record prior to the final ET.  This placement is intentional.  Even if the program ABENDs after the last ADABAS record has been processed, program execution will be in restart mode and the final totals will be correct.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 5    07/07/1997    Sequential match
 
When programmers move from a 3GL to Natural, one of the first techniques to go is the sequential match - the process of reading two files (invariably a master file and a transaction file) sequentially, comparing their keys, and processing matches, widows (masters with no transactions), and orphans (transactions with no master).  There are two reasons for this.  First, it is just too easy to code a READ of the master file with a nested READ or FIND of the transaction file.  Second, it is not possible in Natural to code a true sequential match of two ADABAS files, because the second loop must be closed before the outer loop can be reiterated.  The best you can do is code a step-sequential match (as was already described as a READ with a nested READ or FIND).

The step-sequential method has several problems:

The solution is to process the files as a true sequential match, reading each file only once from start to finish.  This is done by extracting the transaction file to a WORK file.  The READ of the WORK file is nested within the READ of the MASTER file.

The benefits of this approach:

ZZMATCH is a Natural source module which demonstrates a sequential match.

A case study:

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 4    07/07/1997    READ syntax
 
In the READ statement I recommend using the BY keyword instead of WITH and the FROM keyword instead of the equal sign ("=").  I'll explain by using a real-life example.

A programmer coded the following

After some months in production, transaction volumes grew to the point that the programmer determined that performance would improve by changing the FIND to a READ. The programmer did not see the error of his ways until the entire transaction file was deleted.  I believe that the problem would have been avoided had the programmer applied consistency in his use of READ and FIND syntax.  The READ statement should have "looked funny" and caused the code to be questioned.

I too have changed FINDs to READs and vice versa, but I always take the time to change the WITH/BY and "="/FROM keywords.

becomes In the last code segment it is much more obvious that a THRU value is necessary to avoid undesirable results.

I think Software AG made an error in equating FROM and "=" in the READ statement's syntax.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 3    07/01/1997    Replacing certain AT BREAKs with a SORT
 
I like the AT BREAK statement.  Despite some tricky characteristics, and some quirks which make complicated situations even more difficult to code (I'll leave those for a future Tip), I prefer it to the old 3rd-generation method of saving and comparing key values.  It is well worth the effort of learning how to use it.

Coding can be as simple as

On the other hand the AT BREAK statement isn't one that I use daily; invariably I need to refer to the Reference Manual to refresh my memory on its use.  Also DFSORT and SYNCSORT, used by all my clients and most of my previous employers (under MVS; I'm unfamiliar with VSE), are blazingly fast and easier to code in some situations.

This tip deals with replacing the COUNT and SUM functions.  The example which benefits most from this technique is the one in which no appropriate descriptor exists, so the SORT statement or SORT utility must be used.  I'll defer a detailed discussion to a future Tip, but suffice it to say that I always prefer a separate SORT step to an internal sort.  This changes the sample code above to

But since I need to code SORT parameters anyway, I would add This simplifies the second Natural program to You can't get much simpler than that!

I haven't explained where the COUNT field in position 11 of the WORK record came from.  Either add a line containing '00001' to the WRITE WORK statement, or, as I prefer, add an INREC statement to the SORT parameters.

In my own experience, there have been several situations where an existing extract file contained all the data I needed.  Rather than change the Natural extract, which would have required user acceptance testing, I submitted the extract to a sort and used the INREC to append the count field.

There are other examples where AT BREAK was still necessary, but coding was simplified by using the SUM and INREC statements of the SORT utility.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 2    06/30/1997    Continuation Symbol

The continuation symbol (the hyphen '-') is used to code literals which cannot fit on one Natural source line, as in

 ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #A(A100)  INIT<'This is a literal which cannot fit on a '
       - 'single, 72-character, Natural source line.'>

I have four other uses for the continuation symbol.

Consistent indentation

You often see code similar to this

    ....+....1....+....2....+....3....+....5....+....6....+....7..
                   IF some-condition
                     THEN
                       REINPUT
             'A long message that won"t line up properly'
                               ALARM
                   END-IF
 

but I prefer

    ....+....1....+....2....+....3....+....5....+....6....+....7..
                   IF some-condition
                     THEN
                       REINPUT 'A long message that won"t '
                             - 'line up properly'
                               ALARM
                   END-IF
 

Or going back to the first example

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #A(A100)  INIT<'This is a literal which cannot fit on a '
                   - 'single, 72-character, Natural source line.'>
 

Starting value in a READ by superdescriptor

If you're writing an ad hoc to read a superdescriptor, there is no need for the definitions, redefinitions, and assignments typically associated with the READ of a superdescriptor in a production program.  You can specify the starting value within the READ statement itself.

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #SUPER-DEF
      2 #STATE(A2)     INIT<'CA'>
      2 #COUNTY(P3)    INIT<345>
                  1 REDEFINE #SUPER-DEF
      2 #SUPER(A5)
      ...
    READ view BY ST-CTY FROM #SUPER

becomes

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    READ view BY ST-CTY FROM 'CA' - H'345F'
 

If the superdescriptor contains a D-format field, use a utility like ZZDATES to determine the packed value to be specified in the READ.
 

Initialize internal tables
 
Take the superdescriptor example, above, a step further to initialize an array.

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #MONTHS
      2 #MTH(A3/12)     INIT<'Jan', 'Feb', 'Mar', 'Apr',
                             'May', 'Jun', 'Jul', 'Aug',
                             'Sep', 'Oct', 'Nov', 'Dec'>
      2 #DAYS(N2/12)    INIT<30, 28, 31, 30, 31, 30,
                             31, 31, 30, 31, 30, 31>
 

It's easier to see the relationship of "columns" to "rows" like this:

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #MONTHS(A5/12)   INIT<'Jan' - '30',
                            'Feb' - '28',
                            'Mar' - '31',
                            'Apr' - '30',
                             ...
                            'Dec' - '31'>
                       1 REDEFINE #MONTHS
      2 #ELEMENT(12)
        3 #MTH(A3)
        3 #DAYS(N2)
 

For a real-life example of this technique, take a look at the ZZUtils menu program.
 

Impress your friends

Is the following Natural code valid?  If not, why not?  If so, under what conditions, and what, if any, are the results of execution?

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    ASSIGN #A = '456' - '123'
    ASSIGN #B = 'DEF' - 'ABC'
 

Does #A contain '333'?  What about #B?  Solution.
 

Top of Tips & Techniques.  Most recent tip.  Previous tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.


Tip 1    06/10/1997    Initialization of a superdescriptor

All too often, superdescriptor starting values are initialized at execution time.

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #SUPER(A4)  1 REDEFINE #SUPER
      2 #STATE(A2)
      2 #COUNTY(P3)
      ...
    ASSIGN #STATE  = 'CA'
    ASSIGN #COUNTY = 345
    READ view BY ST-CTY FROM #SUPER
 

I prefer compile-time initialization.

    ....+....1....+....2....+....3....+....5....+....6....+....7..
    1 #SUPER-DEF
      2 #STATE(A2)     INIT<'CA'>
      2 #COUNTY(P3)    INIT<345>
                  1 REDEFINE #SUPER-DEF
      2 #SUPER(A4)
      ...
    READ view BY ST-CTY FROM #SUPER
 

Top of Tips & Techniques.  Most recent tip.  Top of this tip.  Next tip.

Return to Ralph G. Zbrog's home page.

Last updated April 25, 1998, by Ralph G. Zbrog.
 



Solution to Tip 2 quiz.

Assuming both #A and #B are of format/length A6, #A contains '456123' and #B contains 'DEFABC'.

Return to Tip 2.