Natural Tips & Techniques - George Lewycky`s Home Page

advertisement
Natural Tips & Techniques
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
Continuation Symbol
2 06/30/1997 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 & Techniques. Previous 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:






It ensures a correct ET (END TRANSACTION) data layout by including the
program name, key value, record count, etc.
It demonstrates the need for a sequence number to uniquely identify WORK
records created during execution.
It includes the ADABAS ISN in ET data to differentiate records with a nonunique key.
It contains optional trace statements for debugging purposes.
It allows the programmer to refresh program status (clear the ET data) for
debugging purposes.
It demonstrates how to determine whether the program is starting or re-starting.
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 READ MASTER/FIND TRANSACTION will not detect orphans; a separate
READ TRANSACTION/FIND MASTER is necessary.
Initiating a READ or FIND loop is much more expensive than a GET NEXT
process. The READ of the MASTER is initiated only once, but the FIND is
initiated once for each MASTER record. For moderate or substantial MASTER
volumes, resources requirements can be very high.
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:



The transaction file is processed in a single invocation of a READ, possibly
PHYSICAL, with PREFETCH/MULTIFETCH.
If no other ADABAS accesses are in the sequential match program, the MASTER
READ may be PREFETCHed/MULTIFETCHed.
Widows and orphans may be determined in a single pass.
ZZMATCH is a Natural source module which demonstrates a sequential match.
A case study:
A client designed a month-end report to display account details. Those details
were stored in a data base of approximately thirty files. A Natural program was
written to read and report the entire master file, drilling down through most of the
twenty-nine associated files. The master loop contained five nested READs, each
in turn containing one to five nested READs or FINDs, and so on. The program
ran very well in test, but by the time the data base contained 100,000 masters and
a million records in all, the program was running over thirty hours on a large
MVS machine.
The program was rewritten as a series of extracts, sorts, and sequential matches.
Physical READs and Prefetch were used extensively. Even after the data base
had grown to 300,000 masters in a data base of six million records, the report ran
consistently in two hours.
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
READ MSTR WITH key
WHERE/ACCEPT/IF ... for selection criteria
FND.
FIND TRAN WITH key = MSTR.key
DELETE(FND.)
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.
READ MSTR WITH key
WHERE/ACCEPT/IF ... for selection criteria
RD.
READ TRAN WITH key = MSTR.key
DELETE(RD.)
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.
READ MSTR BY key
WHERE/ACCEPT/IF ... for selection criteria
FND.
FIND TRAN WITH key = MSTR.key
DELETE(FND.)
becomes
READ MSTR BY key
WHERE/ACCEPT/IF ... for selection criteria
RD.
READ TRAN BY key FROM MSTR.key
DELETE(FND.)
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
READ file BY STATE-COUNTY-SPDE
AT BREAK OF STATE-COUNTY-SPDE
WRITE OLD(STATE)
OLD(COUNTY)
COUNT(TRAN-AMT)
SUM(TRAN-AMT)
END-BREAK
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
READ file PHYSICAL
WRITE WORK 1
STATE
COUNTY
TRAN-AMT
SORT step:
SORT FIELDS=(1,2,A,
3,3,A),
STATE CODE
COUNTY CODE
FORMAT=CH
READ WORK 1
AT BREAK OF #STATE-COUNTY
WRITE OLD(STATE)
OLD(COUNTY)
COUNT(TRAN-AMT)
SUM(TRAN-AMT)
END-BREAK
But since I need to code SORT parameters anyway, I would add
SUM FIELDS=(6,5,PD,
11,5,ZD)
TRANSACTION AMOUNT
COUNT
This simplifies the second Natural program to
READ WORK 1
WRITE #STATE
#COUNTY
#COUNT
#SUM-AMT
END-WORK
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.
INREC FIELDS=(1,10,
C'00001')
ENTIRE WORK RECORD
APPEND A COUNT TO EACH RECORD
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.




To maintain consistent indentation.
To simplify the coding of ad hoc programs which have a starting
value in a READ by superdescriptor.
To make initialized internal tables easier to read.
To impress your friends.
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.
Download