ch20.bodyTEXTDmWr^?By0ymBIN Performance Considerations

Design Considerations

The guidelines presented in this chapter are based on experiences encountered over an extended period of time. Some very important questions need to be asked before getting down to the task of coding:

Modules where sequence does not necessarily matter are unload and/or extract operations where any sequencing required can be accomplished later. Batch "mass" updates of all or a significant portion of the records on a file doesn't necessarily need any sequence of access.

Generally, when sequence doesn't matter, use of the READ IN PHYSICAL SEQUENCE is recommended. It is recommended with caution when updates are to be posted against the file (see READ PHYSICAL later in this chapter).

Never use a READ IN LOGICAL SEQUENCE when performing updates unless there is a primary key in use and that key is not being modified (see READ LOGICAL later in this chapter).

If a specific sequence is desired, examine whether READ IN LOGICAL SEQUENCE BY descriptor will suffice. This returns records in order by that descriptor (key) without having to use any "sort" at all. If the field is a superdescriptor, the READ is equivalent to a multilevel sort.

It is not recommended to use the SORT statement in an update program unless the UPDATE or DELETE is executed before the SORT statement, that is, in the first phase of the program.

It is not recommended to READ LOGICAL BY descriptor and modify the field used to build the descriptor value. The same records could be updated twice (see READ LOGICAL later in this chapter).

HISTOGRAM and Code/Tables Files

HISTOGRAM is an extremely fast method of accessing information held in an Inverted List. No access is made by ADABAS of Data Storage when using this statement thus, only the descriptor values and record counts are available.

One very useful technique that can be used with tables files when translating codes into the extended names is:

File Definition:

CODE A 3 FI     /* TABLE CODE
DESC A 20 /* CODE DESCRIPTION
SUPERDE A 23 /* SUPERDESCRIPTOR = CODE + DESC

Local Data Area Definition:

  :
2 #W-CODE (A3)
2 #W-DESC (A20)
2 #W-SUPERDE (A23)
:

Now, we can use the HISTOGRAM and the superdescriptor to translate the code A01.

DEFINE DATA
LOCAL USING XXXNNNV /* VIEW LDA
2 REDEFINE #W-SUPERDE
3 #W-SUPER-CODE(A3)
3 #W-SUPER-DESC(A20) END-DEFINE

Then

 HISTOGRAM (1) viewname SUPERDE STARTING FROM 'A01'
END-HISTOGRAM

#W-SUPER-DESC contains the translated value of the code. The coding above assumes that the code A01 exists on the file. Depending on the circumstances, you may want to investigate the following alternative:

 0100 HISTOGRAM (1) viewname SUPERDE STARTING FROM 'A01'
0110 END-HISTOGRAM
0120 IF *NUMBER (0100) = 0
0130 REINPUT ...
0140 END-IF
 
 0100 Fview01. FIND NUMBER (1) viewname SUPERDE = 'A01'
0110 IF *NUMBER (Fview01.) = 0
0120 REINPUT ...
0130 END-IF

In the first case, (0100) is used to reference the Line Sequence Number of the access statement when using the *NUMBER System Variable (illustrated by the HISTOGRAM). In the second example a label attached to the statement (the FIND NUMBER for example) is used for referencing.

To solve the opposite problem, that is, you have the description but not the code, an additional superdescriptor would be required with the description field defined as the major component.

FIND NUMBER

Like the HISTOGRAM, FIND NUMBER does not access Data Storage but only interprets the contents of an Inverted List. There are some advantages of a FIND NUMBER vs. HISTOGRAM:

Efficient Use of the FIND Statement

It's important to understand how NATURAL interprets the FIND statement and derives the commands issued internally, the ADABAS commands executed to use the statement efficiently.

The basic syntax of the FIND statement is:

FIND viewname WITH basic-search-criteria-1 (basic-search-criteria-2...)

For a single Basic Search Criteria, ADABAS examines the Inverted List of the descriptor specified and compiles a list of ISNs which satisfy the search criteria using an S1 command. READ BY ISN (L1) commands are then issued to return the records to NATURAL. If the resulting list of ISNs is small, they will be held in core for processing otherwise, the list is written to the Work Data Set and read in as needed. Ideally, the FIND statement should only be used if the number of records to be returned, that is, the number of ISNs, is within the limitations of the NATURAL ISN buffer.

If more than one basic search criteria is specified, then an S1 command for each criteria is issued to compile the appropriate number of qualifying ISN lists. These lists are then ANDed or ORed together according to the rules given in the FIND statement to return a final list of ISNs which satisfy the total search request.

Basic Search Criteria

Basic Search Criteria [NOT] Basic Search Criteria AND
OR
Basic Search Criteria...

Basic search criteria is defined as:

descriptor =
EQ
EQUAL TO
value OR = value
THRU value
BUT NOT value [THRU value]
descriptor

=
EQ
EQUAL TO
¬=
NE
NOT EQUAL
<
LT
LESS THAN
<=
LE
LESS EQUAL
>
GT
GREATER THAN
>=
GE
GREATER EQUAL

value    
descriptor LE
EQ
setname    

 

 

 

 

 

 

 

 

 

 

 

 


Figure 20.1 - Basic Search Criteria

The following example will be resolved by NATURAL as a single Basic Search Criterion, that is, a single S1 Command will be issued to ADABAS:

FIND EMPLOYEES-VIEW WITH NAME = 'SMITH' OR = 'JONES'

But each of the following statements are considered to have two Basic Search Criteria and are resolved as 2 S1 Commands, an S8 command to combine the ISN lists and a further S1 Command to initiate the L1, READ, Commands of the resultant ISN list.

FIND EMPLOYEES-VIEW WITH NAME = 'SMITH' OR NAME = 'JONES'

Or

FIND EMPLOYEES-VIEW WITH (NAME = 'SMITH') OR (NAME = 'JONES')

NATURAL considers the following complex searches even though they produce the same results as the previous FIND:

FIND EMPLOYEES-VIEW WITH NAME = 'SMITH'
AND FIRST-NAME = 'JOHN'

Is considered simple while

FIND EMPLOYEES-VIEW WITH (NAME = 'SMITH')
AND (FIRST-NAME = 'JOHN')

Is a complex search.

Two ISN lists are still built and resolved for the above simple searches but this is done by ADABAS with a single command rather that the 3 commands required for the complex searches. ISN lists for simple searches should be kept as short as possible to avoid excessive I/O to the Work Data Set.

One type of FIND statement that should never be coded:

FIND EMPLOYEES-VIEW WITH NAME NE 'SMITH'

This should, according to the 'manual', be a simple Basic Search Criterion. However, as this is resolved as one S1 command for the entire Inverted List and one S1 of an ISN list of records with the name SMITH which are then resolved with an S8 Command and finally an S1 Command to initiate the L1, READ BY ISN, Commands. Another FIND statement that should never be coded:

FIND EMPLOYEES-VIEW WITH BIRTH GT 901001 AND BIRTH LT 901031

This FIND builds one ISN list of employees born after the first of October, 1990 to the highest birth date in the Inverted List and another ISN list of employees born from the lowest birth date to the 31st of October, 1990. These are then resolved into a final list of ISNs representing employees born in the month of October, 1990. This means that the entire Inverted List has been involved in the processing and those employees born in October twice despite the fact that only one S1 command is used. The correct way to code this search so that only one ISN list is used is:

FIND EMPLOYEES-VIEW WITH BIRTH = 901001 THRU 901031

The search criteria values are included in the search, that is, 901001, 901002, ...901030, 901031.

Avoiding Complex Searches

In order to avoid complex searches and/or reduce excess processing, it is important to know the volumes of data to be extracted by each Basic Search Criterion.

FIND EMPLOYEES-VIEW WITH SEX = 'M'
AND SALARY(1) = '45000 THRU 65000

When the employees file consists of 50% of the records with SEX equal to M and 3% of the records with the SALARY range required, this is more efficiently coded as:

FIND EMPLOYEES-VIEW WITH SALARY(1) WITH 45000 THRU 65000
WHERE SEX = 'M'

Since this only involves processing a single ISN list which is 3% of the total file.

Essentially, only use descriptors in the Basic Search Criteria which significantly reduce the size of the ISN lists to be processed.

A useful rule of thumb is to use 1 search criterion if the number of records it processes, expressed as a percentage of the file, is less than the number of remaining search criterion.

FIND EMPLOYEES-VIEW WITH NAME = 'COLBY'
AND SALARY(1) = 3000

AND COUNTRY = 'USA'
AND SEX = 'M'

Where NAME = 'COLBY' is 2% of the total number of records and COUNTRY = 'USA' is 15% and SALARY(1) = 3000 is 23% and SEX is 50%.

This statement would be better coded as:

FIND EMPLOYEES-VIEW WITH NAME = 'COLBY'
WHERE SALARY(1) = 3000
AND COUNTRY = 'USA'
AND SEX = 'M'

Reading no more than 2% of the records and processing only those that meet the additional criteria.

RETAIN AS Clause

Another useful technique is the use of the RETAIN clause to reduce ISN list processing. This clause enables ISN lists to be saved for future processing therefore avoiding the reprocessing of the same Basic Search Criteria within different loops.

Given the following situation:

FIND EMPLOYEES-VIEW WITH SALARY(1) GT 25000
   AND BIRTH GT 400000
   DISPLAY (1) PERSONNEL-ID NAME FIRST-NAME BIRTH SALARY(1)
END-FIND
FIND EMPLOYEES VIEW WITH SALARY(1) GT 25000
   AND MAR-STAT = 'M'
   DISPLAY (1) PERSONNEL-ID NAME FIRST-NAME BIRTH SALARY(1)
END-FIND

Will generally perform better if coded as:

FIND NUMBER EMPLOYEES-VIEW WITH SALARY(1) GT 25000
   RETAIN AS 'TAXADDS'
FIND EMPLOYEES-VIEW WITH 'TAXADDS'
   AND BIRTH GT 400000
   DISPLAY (1) PERSONNEL-ID NAME FIRST-NAME BIRTH SALARY(1)
END-FIND
FIND EMPLOYEES VIEW WITH 'TAXADDS'
   AND MAR-STAT = 'M'
   DISPLAY (1) PERSONNEL-ID NAME FIRST-NAME BIRTH SALARY(1)
END-FIND
RELEASE SET 'TAXADDS'
END

This method illustrates that the ISN list built in the FIND NUMBER statement is retained for later refinement in each of the following loops. Also, the set is available to any other programs within the application until it is released.

Repeating The Same FIND

Don't pass a key value in a variable if this is to be used in subsequent code to repeatedly FIND the same record. Always pass the ISN and GET the record or if the amount of additional data to be read is small, obtain all the later required fields on the initial database access and pass these so a subsequent GET is unnecessary.

Similarly do not code:

FIND NUMBER EMPLOYEES-VIEW WITH SALARY(1) GT 25000
   IF *NUMBER = 0
   REINPUT ...
   END-IF
   FIND EMPLOYEES-VIEW WITH SALARY(1) GT 25000
   :
    END-FIND
:
END-FIND

Especially with unavoidable complex search criteria since the same ADABAS FIND Commands are repeated. Use an IF NO RECORDS FOUND clause for the above situation.

FIND EMPLOYEES-VIEW WITH SALARY(1) GT 25000
   IF NO RECORDS FOUND
     REINPUT ...
   END-NOREC
   :
END-FIND

In batch (large volumes) look out for the following situation:

FIND view-one WITH field-one
   FIND view-two WITH field-two = view-one.field-one 

Where a given field-one value appears in many view-one records, since this can cause the same set of records from view-two to be found and read repeatedly. In this situation, if possible, code the following

FIND view-one WITH
  :
END-ALL
SORT BY field-one USING...
  AT BREAK OF field-one
     MOVE OLD(field-one) TO #WORK-FIELD
     FIND view-two WITH field-two = #WORK-FIELD
      :
     END-FIND
    :
  END-BREAK
:
END-SORT

READ IN PHYSICAL SEQUENCE

The fastest way of reading an ADABAS file is a READ PHYSICAL since the records are read from Data Storage in the same order as they are stored. No access is made to the Inverted Lists or Address Converter in order to retrieve the records. When reading most of the file data (quite often called the "80% rule" although percentages of records of 50% or 60% is also very workable) it is usually faster to read in physical order and use a WHERE clause to reject unwanted records rather than to use any of the other FIND or READ options. (ADABAS stores more than one record in a block and when read, all records in the block are processed before another I/O to read the next block is issued).

However, this method should not be used where one single physical file contains more than one logical file, as unwanted records may be retrieved. Never read a file in this manner if the records are being concurrently updated. An updated record may increase in overall length as a result of the update and consequently not fit back into its original Data Storage block. This results in it being moved ahead in Data Storage and therefore the record could be read again.

READ IN LOGICAL SEQUENCE

Use a READ LOGICAL rather than a FIND...SORTED BY. This is particularly more efficient when either or both of the following factors are involved:

Note: it is possible to reorder a file on the value of a descriptor but this should only be done if that descriptor is most frequently used for logical reads on large portions of the file and its value is only infrequently updated.

Note: Never read a file in this manner if the records are being con-currently updated. If the value of the descriptor is being amended, this could result in a record being read twice or records not being read at all that should have been prior to the update being applied. The only way to prevent this is to use a FIND as it snapshots an ISN list and then processes the file using this list.

A phonetic descriptor or a descriptor within a periodic group cannot be used to control the sequence of a READ LOGICAL statement. This will result in an ADABAS/NATURAL response code.

READ BY ISN

Reading in ISN order means that no Inverted Lists will be accessed, thus reading in this order in faster than a READ...LOGICAL or FIND provided that the field is physically in ISN order or closely approximates it. Note that a file which frequently has records deleted is likely to have been created to re-use ISNs, in which case, if READ BY ISN is to be used to any great degree it may need periodic reordering to re-establish the ISN order to the physical order. This is clearly impractical for very large database files. Concurrent updating of records will not affect the order in which records are processed when using READ BY ISN.

READ...WHERE

Use of the WHERE clause attaches additional selection criterion to the records read. However, the WHERE becomes an intrinsic part of the READ so that if the records doesn't meet the WHERE criteria, another record is read. Untold numbers of records, potentially the entire file, could be read unless the READ is controlled.

If you've coded a READWHERE and intend to modify the records, Hold Queue considerations must be evaluated. Reading a record for change causes the ISN of that record to be placed in the Hold Queue. If the WHERE criterion is not met, a read of the next record is executed, however, the ISN remains on the Hold Queue. You must control these types if situations or expect the database to die because of Hold Queue overflow (In ADABAS, a parameter can be set monitor an individual User Queue element and only dump that element if the Hold Queue is in jeopardy).

You may consider the use of ACCEPT and/or REJECT statements but the Hold Queue problem still cannot be avoided without coding to control the read and release of the ISN properly.

READ Using Subdescriptors, Superdescriptors and Hyperdescriptors

A READ using a subdescriptor, superdescriptor or hyperdescriptor as a controlling descriptor may be specified with any ENDING AT or THRU value. THRU requires an additional entry in the view in the DEFINE DATA statement block (see below).

It is possible to cause a BREAK condition on a subdescriptor, superdescriptor or hyperdescriptor provided it is specified in the view. In the following example, the superdescriptor uses the entire department field so that by reading in DEPT-PERSON order (department and last name) data is also read in DEPARTMENT order and as DEPARTMENT is a normal field, it is possible to BREAK on it.

DEFINE DATA LOCAL
1 STAFF VIEW OF EMPLOYEES
2 PERSONNEL-ID
2 NAME
2 DEPT
2 DEPT-PERSON
END-DEFINE
READ STAFF BY DEPT-PERSON
STARTING FROM 'SALE00'
ENDING AT 'TECH99'
DISPLAY PERSONNEL-ID NAME DEPT (EM=XXXX^^XX)
AT BREAK OF DEPT-PERSON /6/
WRITE 'PEOPLE IN DIVISION' COUNT(NAME)
END-BREAK
AT BREAK OF DEPT-PERSON /4/
WRITE 'PEOPLE IN DEPARTMENT' COUNT(NAME)
END-BREAK
AT END OF DATA
WRITE 'TOTAL EMPLOYEES' COUNT(NAME)
END-ENDDATA
END-READ
END

If the BREAK is to be on both DEPARTMENT and "SECTION" (department consists of department for four bytes and section for two bytes) and there is no field that can be redefined to establish a break variable, a different user-defined variable should be created. This variable must be assigned its value after the record has been read but before NATURAL checks to see if a BREAK has occurred. This is achieved using the BEFORE BREAK PROCESSING statement since this is executed after NATURAL has read the record but before it checks for the automatic BREAK condition(s).

DEFINE DATA LOCAL
1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
2 PERSONNEL-ID
2 NAME
2 FIRST-NAME
2 DEPARTMENT
2 REDEFINE DEPARTMENT
3 #DEPT(A4)
3 #SECTION(A2)
END-DEFINE
READ EMPLOYEES-VIEW BY DEPT-PERSON
BEFORE BREAK PROCESSING
AT BREAK OF #SECTION
WRITE COUNT(DEPARTMENT) 'STAFF IN SECTION' //
END-BREAK
END-BEFORE
AT BREAK OF #DEPT
WRITE COUNT(DEPARTMENT) 'DEPARTMENTAL STAFF'
NEWPAGE
END-BREAK
END

With subdescriptors, it is only necessary to redefine the parent field from which the subdescriptor was derived in order to determine the value on which to BREAK.

If you only needed to report the statistics, use the HISTOGRAM as an alternative and consider:

AT BREAK field /n/

Our hypothetical superdescriptor is constructed from three fields, department, section and job:

2 DEPARTMENT   /* DEFINED IN THE VIEW AS A4
2 SECTION /* DEFINED IN THE VIEW AS A2
2 JOB /* JOB CODE IS A3 IN THE VIEW
DEFINE DATA LOCAL
1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
2 DEPT-SECTION-JOB
2 REDEFINE DEPT-SECTION-JOB
3 #DEPT(A4)
3 #SECTION(A2)
3 #JOB-CODE(A3)
1 #NUMBER(P7)
END-DEFINE
HISTOGRAM EMPLOYEES-VIEW DEPT-SECTION-JOB
MOVE *NUMBER TO #NUMBER
AT BREAK OF DEPT-SECTION-JOB /3/
WRITE COUNT(#NUMBER) 'STAFF IN JOB' OLD(#JOB-CODE) //
END-BREAK
AT BREAK OF DEPT-SECTION-JOB /2/
WRITE COUNT(#NUMBER) 'STAFF IN SECTION' OLD(#SECTION) //
END-BREAK
AT BREAK OF DEPT-SECTION-JOB /1/
WRITE COUNT(#NUMBER) 'STAFF IN DEPARTMENT' OLD(#DEPT) //
END-BREAK
END-HISTOGRAM
END

Unnecessary Loops

Do not make NATURAL set up loops unnecessarily, for example, if the ISN is known on a file, use:

GET ... ISN

Not

READ ... BY ISN
STARTING FROM #ISN
ENDING AT #ISN

However, be aware that use of a GET rather than a READ will demand that explicit (back) references or label qualifier will be needed if the GET is itself contained within another loop.

Validation Using FIND, READ and HISTOGRAM

Various techniques can be used to check whether values exist on an ADABAS file. Consider the following alternatives;

FIND NUMBER viewname WITH descriptor EQ value

Only uses the Inverted List(s) for validation provided there is no WHERE clause. No data is read from Data Storage therefore the access is logical rather than physical. No processing loop is created. (Note: the FIND NUMBERWHERE clause is not permitted in Structured Mode).

FIND (1) viewname WITH descriptor EQ value

Locates one record satisfying the criteria. A processing loop is created and is passed through once if there is a qualifying record. Database fields are available from the record read within the loop only. Note: if 300 records meet the basic search criteria, this is the number of ISNs returned; (1) only processes the first (lowest) ISN from the list.

READ (1) viewname BY descriptor
STARTING FROM ...
ENDING AT ...

(Where the descriptor is not from a multiple value field, a sub-descriptor, superdescriptor, hyperdescriptor, phonetic descriptor not contained within a periodic group) READ's one qualifying record only. A processing loop is created and within the loop database fields are available from the record read. Starting and ending values must be specified as the same if a specific value is to be validated as present on file.

HISTOGRAM viewname  FOR descriptor
STARTING FROM ...
ENDING AT ...

(Where the descriptor is not contained within a periodic group) uses an Inverted List only. A processing loop is created and provided there is a qualifying value is passed through once. No database fields are available other than the descriptor value specified since Data Storage has not been read. If the descriptor is contained within a periodic group then an index value may be specified to check only for values in a particular occurrence, hence a single pass of the loop if a matching value is found.

HISTOGRAM viewname FOR descriptor(i)
   STARTING FROM ...
   ENDING AT ...

Uses an Inverted List also and if all occurrences are to be considered in the validation process then a limit of (1) needs to be set for a single pass of the loop else it will execute the number of times the value appears in different occurrences:

HISTOGRAM (1) viewname FOR descriptor
   STARTING FROM ...
   ENDING AT ...

Until now, the examples given are for checking the presence of a particular value on a file. Consider the situation where the validation requires determination of the presence of a value greater than a given value.

FIND NUMBER EMPLOYEES-VIEW WITH NAME GT 'SMITH'
   IF *NUMBER GT 0
     WRITE 'RECORDS FOUND'
   END-IF
   :
END-FIND

Reads the entire NAME Inverted List for values greater than SMITH.

READ EMPLOYEES-VIEW BY NAME
   STARTING FROM 'SMITHA'
   WRITE 'RECORDS FOUND'
   ESCAPE BOTTOM
END-READ

Makes an initial positioning probe in the Inverted list and then READ's one record from Data Storage.

HISTOGRAM EMPLOYEES-VIEW FOR NAME
   STARTING FROM 'SMITHA'
:
END-HISTOGRAM

Makes an initial positioning probe in the Inverted List only.

If the number of entries greater than SMITH is at all significant then the second and third examples are more efficient that the first example but note that the second does read a record from Data Storage.

CALLing NATURAL Objects

Within NATURAL 2 there are a number of NATURAL object types that can be called from other object types via different statements. The relationship between these objects types and the statement to invoke each is:

 
Subroutine
  Program Subprogram Internal

External

FETCH Yes No No No
FETCH RETURN Yes No No

No

CALLNAT No Yes No No

PERFORM

No No Yes Yes

 

 

 

 

 

 


Parameter Passing Rules

FETCH

The following rules apply when passing parameters between the various NATURAL object types.

FETCH program-name [parameter...]
FETCH RETURN program-name [parameter...]

Invoking a NATURAL program from another NATURAL Object requires the execution of a FETCH or FETCH RETURN statement.

Specifying a parameter list requires an INPUT statement in the invoked program to accept the passed values. These must match in number, format and length for consistent results (if too many parameters are supplied, the extras are ignored; if too few are supplied, the extra fields are null filled). The values are passed to the invoked program through NATURAL's STACK.

The more efficient technique for passing values is through the Global Data Area. Any changes which take place in the common fields addressed through the GDA are immediately reflected in the GDA accessed and shared by each invoked object.

CALLNAT

CALLNAT subprogram-name [parameter-list... (AD= )] 

The invoked subprogram requires a DEFINE DATA PARAMETER which must correspond exactly in number, format and length to the passed parameters (CALLNAT's field list). The data which is passed can be given an attribute of modifiable (AD=M) or output only (AD=O), in order to determine if any changes made in the invoked subprogram are available to the calling object.

PERFORM

PERFORM subroutine-name [parameter...]

Common data fields may be defined in a Global Data Area or an argument list in order for the data to be passed between the invoking object and the subroutine.

When coding subroutines internal to the NATURAL object, the GDA isn't necessary for passing values since access to all data in the module is permitted for the internal subroutine.

Alternatively, a Parameter Data Area may be used to accept passed data in an external subroutine; the same rules that apply to subprogram PDAs apply to subprogram PDAs.

Use of External NATURAL Objects

The modularization of NATURAL code into subprograms, subroutines and separate programs should take a number of factors into consideration.

From a performance point of view, the passing of a storage address between objects is far more efficient that the passing of a number of data values. Therefore, from this perspective, the PERFORM and the FETCH RETURN (without parameters) are the approach to take, since only the storage address of the GDA is passed. If a NATURAL object is to be used by more than one object, then it should be able to accept data and complete its processing and return to the calling object without changing the GDA which was in use at the time of the call. The CALLNAT statement, with its ability to accept parameter data and its facility to create a separate GDA (if required), provides this function. This feature can prove to be very useful when several variables from the current function needs to be passed to a lower level module and read back.

If a NATURAL program has to be executable as a separate function or as a function within an application, a separate program should be used and the linking within the application achieved with the FETCH RETURN statement. Avoid the FETCH statement unless required since it is destructive, that is, processing returns to level 1 and the GDA is initialized anew.

CALL

Use of the CALL statement is recommended only when a function not supported by NATURAL is needed. Accessing ISAM or VSAM files or job submission through the internal reader or routing specifications for printer control seem to be the only real use for the CALL statement currently.

Arithmetic Statements

To achieve the best performance, all arithmetic should be done using packed format variables. The number of decimal places in each variable should agree wherever possible.

In expressions where formats are mixed between numeric and/or packed and floating point formats then a conversion to floating point format is performed by NATURAL.

A conversion from numeric/packed to floating point results in considerable CPU usage and therefore mixed format expressions should be avoided.

Where possible, use ADD, SUBTRACT, MULTIPLY or DIVIDE instead of using the COMPUTE statement which causes more machine instructions to be produced.

For example, if a simple addition is required, use the ADD statement instead of COMPUTE:

COMPUTE #OFFSET EQ #BASE + #LINE

Will compute the value of the user variable #OFFSET based upon the values in the user variables #BASE and #LINE.

ADD #BASE #LINE GIVING #OFFSET

Will add the contents of user variables #BASE and #LINE and store the result in #OFFSET. The results will be the same as the COMPUTE with far less processing overhead.

The ROUNDED clause is available to arithmetic statements to insure correct calculations when limiting the number of decimal places.

ADD ROUNDED #BASE #LINE GIVING #OFFSET

The SUBTRACT statement is just as flexible:

SUBTRACT #BASE #LINE FROM #TOTAL GIVING #OFFSET

Will add the contents of user variables #BASE and #LKINE and subtract the total from #TOTAL storing the result in #OFFSET.

Work File Records

When data is being written to and from work files using READ WORK FILE or WRITE WORK FILE, using a few large variables that can be redefined rather than using a large number of short variables will prove more efficient.

DEFINE DATA LOCAL
1 #W-RECORD-DATA1 (A80)
1 REDEFINE #W-RECORD-DATA1
2 #RECORD-FIELD1(N7)
2 #RECORD-FIELD2(A20)
2 #RECORD-FIELD3(A35)
END-DEFINE
READ WORK FILE 1 RECORD #W-RECORD-DATA1
:
END-READ
END

Where WORK FILE 1 is the dataset specified as CMWKF01 in the batch JCL and #W-RECORD-DATA1 is the user variable containing the record data. The RECORD clause provides for even more efficient transfer of data and should always be used.

DEFINE DATA LOCAL
1 #W-RECORD-DATA2 (A80)
1 REDEFINE #W-RECORD-DATA2
2 #RECORD-FIELD1(N7)
2 #RECORD-FIELD2(A20)
2 #RECORD-FIELD3(A35)
END-DEFINE
WRITE WORK FILE 2 #W-RECORD-DATA2
:
END

Where WORK FILE 2 is the dataset specified as CMWKF02 in the batch JCL and #W-RECORD-DATA2 is the user variable containing the record data.

COMPRESS Statement

Avoid repeated COMPRESS operations if a MOVE will do.

COMPRESS 'FIXED LENGTH VARIABLE' #VARIABLE INTO #MESSAGE

Is handled more efficiently as:

DEFINE DATA LOCAL
1 #W-MESSAGE (A80)
1 REDEFINE #W-MESSAGE
2 #W-TEXT(A21)
2 #W-MSG (A35)
1 #W-VARIABLE(A35)
END-DEFINE
MOVE 'FIXED LENGTH VARIABLE' TO #W-TEXT
MOVE #W-VARIABLE TO #W-MSG
:
END

Rather than use COMPRESS prior to a WRITE statement, a single PRINT statement may often be used to accomplish the same task.

COMPRESS #W-TEXT #W-MSG INTO #W-VARIABLE
WRITE 'VALUE OF VARIABLE' #W-VARIABLE

But

PRINT 'VALUE OF VARIABLE' #W-TEXT #W-MSG

COMPRESS should not be used to form descriptors. Use it to combine text only.

SORT Statement

If a particular sequence of data is required within a program and it is not possible to obtain the data in that sequence then SORT should be used.

It is important to note some of the restrictions associated with the SORT statement:

It is highly desirable to keep the SORT record as short as possible. Use of the SORT Statement:USING clause is required in Structured Mode programs and should also be used in Report Mode programs.

With on-line programs SORT invokes the NATURAL sort program. In batch, the sort program provided by the Operating System will be invoked.

Example of SORT:

FIND EMPLOYEES-VIEW ...
:
END-ALL /* REQUIRED
SORT BY NAME
USING FIRST-NAME CITY /* REQUIRED
DISPLAY NAME FIRST-NAME CITY
END-SORT /* REQUIRED
END

Multiple Value Fields and Periodic Groups

There are three ways in which multiple value fields and periodic groups may be retrieved from a database record:

Variable ranges of indices and variable starting point should not be used together due to the processing involved. NATURAL has to retrieve all the possible occurrences for the multiple value field and/or periodic group.

Depending upon the number of multiple value field or periodic group entries that need to be processed or that exist on file, one of the above methods should be used.

To help determine which to use, the general rule is to keep the number of multiple value field or periodic group entries to a minimum.

Example: Fixed range of indices:(MUs and PEs) and fixed starting point:(MUs and PEs).

DEFINE DATA LOCAL
1 #I-COUNT(P3)
1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
2 PERSONNEL-ID
2 NAME
2 C*INCOME
2 SALARY(1:6)
END-DEFINE
READ EMPLOYEES-VIEW MOVE C*INCOME TO #I-COUNT
DISPLAY PERSONNEL-ID NAME SALARY(1:#I-COUNT)
END-READ
END

Example: Fixed range of indices but variable starting point:(MUs and PEs).

DEFINE DATA LOCAL
1 #I-COUNT(P3)
1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
2 PERSONNEL-ID
2 NAME
2 C*INCOME
2 SALARY(#I-COUNT:#I-COUNT+1)
END-DEFINE
MOVE 5 TO #I-COUNT
READ EMPLOYEES-VIEW
MOVE C*INCOME TO #I-COUNT
DISPLAY PERSONNEL-ID NAME
SALARY(#I-COUNT.1)
SALARY(#I-COUNT.2)
END-READ
END

The Format Buffer for SALARY is always 5-6. #I-COUNT.n is #I-COUNT offset by 'n' occurrences. The value for 'n' must never exceed ((maximum-occurrence - minimum-occurrence) + 1), i.e., in the above example, ((12-11)+1) = 2. I #I-COUNT must be defined before it is used in the specification.

Example: Variable range of indices:(MUs and PEs) and variable starting point.

DEFINE DATA LOCAL
1 #I-COUNT1(P3) INIT<1>
1 #I-COUNT2(P3)
1 #FOR-LOOP(P3)
1 EMPLOYEES-COUNT VIEW OF EMPLOYEES
2 C*INCOME
1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
2 PERSONNEL-ID
2 NAME
2 SALARY(#I-COUNT1:#ICOUNT1+#I-COUNT2)
END-DEFINE
REMPL01. READ EMPLOYEES-COUNT
MOVE C*INCOME TO #I-COUNT2
GEMPL01. GET EMPLOYEES-VIEW *ISN(REMPL01.)
FOR #FOR-LOOP #I-COUNT1 TO #I-COUNT2
DISPLAY PERSONNEL-ID C*INCOME
SALARY(GEMPL01./#I-COUNT1.#FOR-LOOP)
END-FOR
END-READ
END

The format buffer for SALARY is always 1 thru 99 and therefore this method increases rather than decreases the number of occurrences returned and the processing overhead.

SKIP Statement

The SKIP statement is used to generate one or more blank lines in an output report. It may only be used in conjunction with a WRITE, WRITE TITLE, DISPLAY or PRINT statement using the following syntax:

SKIP [report-number] numeric-constant-or-numeric-variable

Where report-number is an optional report identifier followed by a variable or constant indicating the number of blank lines requested.

Always use SKIP 1 rather than WRITE ' ' if a blank line is required after a DISPLAY. WRITE may use slashes for inserting blank lines as part of the statement so it isn't necessary to code more than two WRITE statements with an intervening SKIP statement to create blank lines.

WRITE variable1 variable2 variable3 /// variable4 variable5

Instead of

WRITE variable1 variable2 variable3
SKIP 2
WRITE variable4 variable5

However, if an END OF PAGE message or the output of a WRITE TRAILER is required to be written on the last line of the physical page even when an END OF DATA condition is reached, then code as follows:

 :
AT END OF PAGE
WRITE 'END OF PAGE'
END-END-ENDPAGE
AT END OF DATA
REPEAT UNTIL *LINE-COUNT = *PAGESIZE
SKIP 1
END-REPEAT
END-ENDDATA
:

As an alternative:

DEFINE DATA
:
1 #LINES-LEFT(P3)
:
AT END OF PAGE
WRITE 'END OF PAGE'
END-END-ENDPAGE
AT END OF DATA
SUBTRACT *LINE-COUNT FROM *PAGESIZE GIVING #LINES-LEFT
SKIP #LINES-LEFT
END-ENDDATA
:

These techniques are used to eliminate the drifting WRITE TRAILER output placing it at a "fixed" position on the last page of the report.

SET TIME and Performance Measurement

A useful technique for identifying programs which take a long time to execute in an on-line session is to use the SETTIME statement and *TIMD System Variable (*TIMD is only available to the SETTIME statement):

SETTIME
:
WRITE *PROGRAM *TIMD (EM=99:99:99'.'9)
:
END

Will return the total elapsed time of the execution of a program, including the wait time for a response to an INPUT statement. To exclude the wait time, use the following construct;

SETTIME
:
WRITE *PROGRAM 'SECTION 1' *TIMD (EM=99:99:99'.'9)
INPUT
SETTIME
:
WRITE *PROGRAM 'SECTION 2' *TIMD (EM=99:99:99'.'9)
:
END

Similarly, programs with FETCH RETURN, CALLNAT or PERFORM of other external objects should also have the time measurement divided into separate sections.

Referencing/Qualifying

Back referencing or qualifying is accomplished three ways in NATURAL programs:

Example of line sequence number referencing.

0010 DEFINE DATA LOCAL
0020 1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
0030 2 PERSONNEL-ID
0040 2 NAME
0050 END-DEFINE
0060 FIND EMPLOYEES-VIEW WITH NAME = 'HAMILTON'
0070 DISPLAY PERSONNEL-ID(0060)
0080 END-FIND
0090 END

Example of Label References

The period must always be used with the label. Labels and line sequence number references cannot be intermixed in the same loop.

0010 DEFINE DATA LOCAL
0020 1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
0030 2 PERSONNEL-ID
0040 2 NAME
0050 END-DEFINE
0060 FEMPL01. FIND EMPLOYEES-VIEW WITH NAME = 'HAMILTON'
0070 DISPLAY PERSONNEL-ID(FEMPL01.)
0080 END-FIND
0090 END

Example of(Level 1) Qualifiers

0010 DEFINE DATA LOCAL
0020 1 EMPLOYEES-VIEW VIEW OF EMPLOYEES
0030 2 PERSONNEL-ID
0040 2 NAME
0050 END-DEFINE
0060 FIND EMPLOYEES-VIEW WITH NAME = 'HAMILTON'
0070 DISPLAY EMPLOYEES-VIEW.PERSONNEL-ID
0080 END-FIND
0090 END

Qualifiers, quite simply, are the level 1 view names or group names used to qualify a field within that domain. Qualification is recommend anytime there is any doubt about readability.

While not always required, there are a few instances where their use is necessary. All fields obtained from a GET statement should be qualified to the GET statement.

0100 GET EMPLOYEES-VIEW *ISN
0110 DISPLAY SALARY(0100/6:12)
or 0100 GEMPL01. GET EMPLOYEES-VIEW *ISN
0110 DISPLAY SALARY(GEMPL01./6:12)
This saves both confusion in the logic and time in the syntax checker.

References should also be used in nested loops when using fields from the most recently open (active) loop.

The System Variables *NUMBER, *COUNTER and *ISN should always be coded with refer backs, especially is coded outside the active loop.

Note: a loop will not be entered if no records meet the criteria or if the 'terminating condition' is already true.

0100 MOVE 'UNKNOWN' TO #W-DESCR
0110 FIND (1) EMPLOYEES-VIEW WITH NAME = #W-DESCR
0120 DISPLAY PERSONNEL-ID
0130 END-FIND

Line 0120 will not be executed if no match is found at line 0110.

The only way of forcing NATURAL to enter a loop when no records were found is to use IF NO RECORDS FOUND. This will cause the loop to be entered once only. All database fields will be null (blank or zero) and both *COUNTER and *NUMBER will be zero.

0100 FIND EMPLOYEES VIEW WITH NAME = 'HAMILTON' 
0110   IF NO RECORDS FOUND 
0120     MOVE 'NAME NOT ON FILE' 
0130   END-NOREC 
0140   WRITE NAME *COUNTER 
0150 END-FIND  

Note: you can check if a loop was entered, i.e., if any records were found, by examining *COUNTER and/or *NUMBER after the loop has ended, as:

0100 FIND EMPLOYEES VIEW WITH NAME = 'HAMILTON'
0110 WRITE NAME
0120 END-FIND
0130 IF *NUMBER(0100) EQ 0
0140 REINPUT 'NO MATCH FOUND'
0150 END-IF

The ENTER clause of the IF NO RECORDS FOUND condition allows the loop to be entered directly with not statements are needed as part of the condition.

0100 FIND EMPLOYEES VIEW WITH NAME = 'HAMILTON'
0110 IF NO RECORDS FOUND
0120 ENTER
0130 END-NOREC
0140 WRITE NAME *COUNTER
0150 END-FIND

Field Formats

Long literals (particularly spaces) should be avoided for space conservation. Use 'nX' or 'nT' notation for spacing in titles, trailers and detail output. Use 'T*' to aid in positioning totals and summary data for proper alignment without having to use large edit masks or spaces in literals to effect the positioning required.

Another note on T*. Edit masks should never be coded larger than the data of the field to which they are to apply. NATURAL will disregard any additional characters. Historically, programmers did this to align, usually decimally, the report totals. Use of an edit mask the size of the data in conjunction with T* is the NATURAL way to solve this alignment dilemma.

Use packed variables instead of numeric format in arithmetic statements. It is more efficient to use variables of the same length and format in an arithmetic operation as mentioned earlier.

Use database fields within your program instead of moving them to work fields wherever possible. This saves space and does not affect the database unless an UPDATE is issued.

 

 

2e_x_photoshop.load_action", 4); user_pref("mime.image_x_photoshop.mac_appname", ""); user_pref("mime.image_x_photoshop.mac_appsig", "????"); user_pref("mime.image_x_photoshop.mac_filetype", "????"); user_pref("mime.image_x_photoshop.mac_plu Dreamweaver28A2STR x