[Company Logo Image]

 
       
Level Breaks in LANSA  
  Home Up Contents  
         

 

 

Home
Up

 


www.can-da.com

 

Overview:

This paper discusses the use of Level Breaks in LANSA. LANSA is a sequential language unlike RPG and as such the ability to perform calculations when a value changes is not intrinsic to LANSA. Despite this, LANSA does provide that facility through the use of report BREAKS and associated sub-routines. This functionality can be very powerful. However, it does require a knowledge of BREAK handling by LANSA. This paper attempts to provide an appropriate level of knowledge to utilize BREAK handling to its fullest.

 

Introduction:

A common theme during the production of reports is the presence of sub-totals, and other segmentation of the details contained on the report. In older forms of RPG these were referred to as level breaks. Often they are accompanied by special processing.

In cyclic languages, such as RPG, there is typically a built in capability to determine when a level break has occurred and to take special action at that time. The resulting code is very simple and might look something like the following:

L2 KEYA CHAIN FILEA
L1 EXSR BREAKLO

where the L1 and L2 are indicators used to determine that a break has occurred. In sequential languages, such as RDML, C, COBOL, and BASIC, in order to provide that functionality it is necessary to code the break checking using brute force. The resulting pseudo-code might look like the following:

if KEYA <> KEYAOLD
    chain to FILEA using KEYA
end if
if (KEYA <> KEYAOLD) or (KEYB <> KEYBOLD)
    call subroutine BREAKLO
end if
KEYAOLD = KEYA
KEYBOLD = KEYB

As you can see, the resulting code is considerably more verbose than the cyclic language. As the number of levels increases, the complexity increases exponentially. If printing is involved then the processing becomes even more complex as it becomes necessary to ensure that the correct version of data is printed. Examples of this are left to the detailed logic presentation.

LANSA provides a capability to perform sub-totals for printing. It does this through the DEF_BREAK statement. By providing both before details (*LEADING) and after details (*TRAILING) versions, LANSA is capable of providing most normal report requirements for break handling. By combining this with the KEEP_xxxxx commands and the KEEP_LAST parameter in the FETCH command it is possible to perform the most common break handling functions without having to code any special break handling.

However, there are many cases where more complex coding beyond a simple file read and/or a print line is required.

There are three basic solutions to providing this:

  1. Ignore the level break
  2. Use brute force
  3. Use the Subroutine from a DEF_BREAK line

Ignoring the level break is not as foolish as it seems. Often the only reason for doing the code at level break time is because of a total needing to be cleared at break time or processing efficiency. LANSA provides a solution for each of these. In order to calculate totals which need to be cleared at breaks LANSA provides the KEEP_xxxxx commands. There are several versions which will keep counts, totals, averages, maximum and minimum and then reset when a set of fields change. By judicious use of the SELECT statement and the KEEP_xxxxx commands it is possible to do most sub-total calculations with minimal coding. Efficiency of code is often a misused term. The primary source of coding (in)efficiency is I/O. Only if there is a high volume of code AND a high volume of transactions, will non-I/O commands materially affect the execution time of the code. LANSA provides the ability to retain the last x number of records fetched. This eliminates most of the I/O as a source of level break inefficiency. Very little will be achieved by trying to remove the remaining non-I/O command inefficiencies and the cost of testing will often exceed the cost of performing the commands.

Sometimes it is not possible to ignore the level break. This may occur if special page handling is occurring or if a SELECT within a select is occurring or for many other reasons. If there is only one level break involved the brute force method, discussed above, may be appropriate. Generally, this involves the retention of prior values of the level break fields and comparison to the current values.

If there are a large number of breaks, then it may be appropriate to use the Sub-Routine parameter within the DEF_BREAK command. This parameter assigns a sub-routine to be initiated whenever the break line is to be printed. These sub-routines have a number of special restrictions:

bulletThey cannot have parameters
bulletThey SHOULD restrict themselves to simple manipulations of fields that are to be printed on the break line. Fields other than those that appear on the print line may be changed but not in ways that are expected to communicate information to other parts of the RDML function at some later time.
bulletThey SHOULD avoid executing other subroutines.
bulletThey SHOULD avoid any screen panel interactions.
bulletThey SHOULD avoid printing any type of information at all.

Only the first restriction is enforced. The others can be performed if the process is sufficiently understood. HOWEVER, do not execute other subroutines and do not perform screen panel interactions. In the former, it is too easy to lose track of what is included in the break subroutine. In the later, it is too easy to cause invalid data to be printed.

This paper will attempt to give you sufficient information to understand the capabilities and limitations to field manipulations and printing which gives rise to those restrictions. Once you understand the reason for the rules you will know when to ignore them.

 

 

A Simple Example:

Let’s start with a simple example, a function which will read a file with three fields, print the fields, and print a sub-total line and a batch header line when one of the fields changes and a new page when the other changes. The RDML would look something like this:

FUNCTION OPTIONS(*DIRECT)
GROUP_BY NAME(#FILEA) FIELDS(#KEYA #KEYB #DATA)
DEF_HEAD NAME(#HEAD) FIELDS(#FUNCTION #REP1PAGE
#KEYA) TRIGGER_BY(#KEYA *OVERFLOW)
DEF_BREAK NAME(#BEFORE) FIELDS(#KEYB) TRIGGER_BY(#KEYB)
TYPE(*LEADING)
DEF_LINE NAME(#LINE) FIELDS(#KEYA #KEYB #DATA)
DEF_BREAK NAME(#AFTER) FIELDS(#KEYB) TRIGGER_BY(#KEYB)
TYPE(*TRAILING)
************
SELECT FIELDS(#FILEA) FROM_FILE(#FILEA)
PRINT LINE(#LINE)
ENDSELECT
************
RETURN

Notice that there is a logic error. The final totals will not print because there is an ENDPRINT missing. This has been left off for clarity sake.

Let's look at how LANSA would compile this into pseudo code .... (click on the object to see a larger version) ...:

An interesting thing to observe in this is that while the Detail line (#LINE) and the Leading Break line (#BEFORE) print the current values of the fields, the Trailing Break line (#AFTER) actually prints a work field which was set on the previous cycle. Another interesting point is that overflow testing occurs at the beginning of each DEF_xxxxx command. Notice that in the Trailing Break Header code, the KEYA test is actually testing the prior KEYA and the current saved KEYA. Therefore, only the line level breaks will actually work. In this way, page breaks (Header lines) other than overflow will print only after the Trailing Break lines have printed. Finally notice the order of executing the print lines: *TRAILING, *LEADING, Detail.

 

Using A Subroutine

Now for a slightly more complex model. In this one we will add a subroutine to the break line and a value to be printed which will be set in the subroutine. We will also add two commands to illustrate where main line commands occur. The RDML code would look as follows:

FUNCTION     OPTIONS(*DIRECT)
GROUP_BY     NAME(#FILEA) FIELDS(#KEYA #KEYB #DATA)
DEF_HEAD     NAME(#HEAD) FIELDS(#FUNCTION #REP1PAGE
             #KEYA) TRIGGER_BY(#KEYA *OVERFLOW)
DEF_BREAK    NAME(#BEFORE) FIELDS(#KEYB #DATABB)
             TRIGGER_BY(#KEYB) TYPE(*LEADING)
             SUBROUTINE(SUB_BEF)
DEF_LINE     NAME(#LINE) FIELDS(#KEYA #KEYB #DATA)
DEF_BREAK    NAME(#AFTER) FIELDS(#KEYB #DATABA)
             TRIGGER_BY(#KEYB) TYPE(*TRAILING)
             SUBROUTINE(SUB_AFT)
************
SELECT       FIELDS(#FILEA) FROM_FILE(#FILEA)
CHANGE       FIELD(#A) TO(1)
PRINT        LINE(#LINE)
CHANGE       FIELD(#A) TO(2)
ENDSELECT
************
RETURN
************
SUBROUTINE   NAME(SUB_BEF)
FETCH        FIELDS(#DATABB) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE

************
SUBROUTINE   NAME(SUB_AFT)
FETCH        FIELDS(#DATABA) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE

Let's look at how LANSA would compile this into pseudo code (changes are highlighted).... (click on the object to see a larger version) ...:

 

Notice a number of interesting conclusions about this code which explains why LANSA recommends that some things should not be done. Notice how the page overflow always occurs at the beginning of the command group. Therefore, if a Break subroutine prints, the overflow capability no longer exists. Also notice how the Trailing Break logic manipulates fields. By printing the "_after" versions of the fields it ensures that the previous version of the field will be printed. However, it also means that any printed fields must be updated after the Break subroutine and any previous versions reset to their current version. This is why there is a save and restore of all fields before and after the break subroutine.

Notice, however, that the restore creates a number of important situations. It effectively prevents the communication from the subroutine to the rest of the function. Thus if you read a file within a Trailing Break subroutine any data received will not be passed along. Similarly, if you wish to set on a flag (e.g., force a page feed), the flag will not be passed to any subsequent commands. Notice also that the only fields passed to the subroutine correctly are those set within the subroutine (e.g., by a FETCH) or those being printed. This includes the page control information (e.g., current line which becomes the last line printed by a normal print line).

Another interesting observation is that the field manipulation related to Trailing Breaks does not occur for Leading Breaks. This means that Leading Break subroutines are not subject to the same data manipulation and communications restrictions as Trailing Breaks. So if we need to set on a flag such as a force page feed, we need to do it during a Leading Break subroutine.

Also observe that the processing of Break subroutines occurs after the page header logic, and that the page header logic is repeated for at the beginning of each print definition. This is why LANSA recommends that you not try to print anything inside a Break Subroutine. Notice also that header logic will occur once per DEF_xxxxx no matter how many print lines are in the DEF_xxxxx. Note that the actual generated code calculates the overflow so that all lines in a single DEF_xxxxx are printed together.

 

Observations & Applications:

  1. Before using Break logic, within a subroutine or otherwise, always question if it is really needed. Would an alternative technique work? Does the benefit justify the cost? Is this just a "fancy"? For example, would a KEEP_LAST achieve almost the same level of efficiency? Would a description on the total line mean that totals would not need to have an attached header when printing on new pages? Does it really matter that totals are not "orphaned" (ie, printed on the next page)? Does it really matter that headers are "orphaned" (ie, printed on one page with details and totals on the next)? Would printing the header at the top of the page be good enough?
  2. When using a *TRAILING Break Subroutine, ensure that all fields to be used in the subroutine are included in the print line.
  3. If you must use fields not in the print line inside a *TRAILING Break Subroutine, you need to be aware of when those values were set, (i.e., as of the moment just prior to the PRINT command). In general, they will apply to the current cycle rather than the prior. This is extremely important to remember when multiple Break lines exist.
  4. When using a *TRAILING Break Subroutine, remember that the subroutine will not be run the first time through the logic.
  5. When using a *TRAILING Break Subroutine, treat it as though it was an independent, isolated routine with the print fields acting as its parameter list.
  6. If you want to process data only at a break, use a *LEADING Break subroutine. The data will be available to the rest of the function.
  7. If you want to force a print based on a flag, set the flag on a prior step. In other words, in the main logic before the print command, or if you are doing it at a break, in a PREVIOUS *LEADING Break subroutine.
  8. There is no rule saying a print line needs to print anything .... For example, the command:
  9. DEF_BREAK NAME(#NOTHING)
              FIELDS(#BLANK) SPACE_BEF(0)
              SPACE_AFT(0) IDENTIFY(*NOID)
  10. will not actually print anything. It does, however, generate the code appropriate to a Break line and will generate an I/O to the print file.
  11. You can print within a Break subroutine by using the SKIP (skips to a particular line) or the SPACE (line feeds), if you are careful. In a *TRAILING Break subroutine the print line control will be set to the current line immediately after the command is executed. Unfortunately, you cannot SPACE NUM_LINES(0). Note that this can be dangerous since the Header handler is not executed after the SKIP or SPACE and does not allow for the extra lines when calculating overflow requirements. It is therefore possible to print a page without headings when this technique is used.
  12. The actual page overflow calculation is hard-coded by the compiler. For example, if the page overflow line is 60, and the DEF_xxxxx group has 3 lines, the code will compare the current print line to 57 not to Overflow_Line - 3. Note, that this means that if you change the print file’s overflow line it will NOT automatically have an effect. You will need to re-compile in order to change the overflow point.

 

Examples of Application: Comparision of two solutions to "orphan" totals.

Problem:

Using the above code, if a detail line prints on the last line the totals will print on the next page without and the user may not be able to determine what KEYB they go with.

Example 1: The simple way.

Solution Method:

Print the headers regardless of breaks, if a new page is to be printed.

Solution Code:
FUNCTION     OPTIONS(*DIRECT)
GROUP_BY     NAME(#FILEA) FIELDS(#KEYA #KEYB #DATA)
DEFINE       FIELD(#NEWPAGE) TYPE(*DEC) LENGTH(15) DECIMALS(0)
             DESC('Force New Page')
DEFINE       FIELD(#OVERFLOW) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC('Page Overflow Line')
DEFINE       FIELD(#PRINTLINE) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC(‘Current Print Line’)
DEF_REPORT   RET_OVERF(#OVERFLOW) RET_LINE(#PRINTLINE)
DEF_HEAD     NAME(#HEAD) FIELDS(#FUNCTION #REP1PAGE
             #KEYA) TRIGGER_BY(#KEYA *OVERFLOW)
DEF_BREAK    NAME(#BEFORE) FIELDS(#KEYB #DATABB)
             TRIGGER_BY(#KEYB #NEWPAGE) TYPE(*LEADING)
             SUBROUTINE(SUB_BEF)
DEF_LINE     NAME(#LINE) FIELDS(#KEYA #KEYB #DATA)
DEF_BREAK    NAME(#AFTER) FIELDS(#KEYB #DATABA)
             TRIGGER_BY(#KEYB) TYPE(*TRAILING)
             SUBROUTINE(SUB_AFT)
************
SELECT       FIELDS(#FILEA) FROM_FILE(#FILEA)
CHANGE       FIELD(#A) TO(1)
IF           (#PRINTLINE = #OVERFLOW - 3) /* -3 is footer allowance */
CHANGE       FIELD(#NEWPAGE) TO(‘#NEWPAGE + 1’)
ENDIF
PRINT        LINE(#LINE)
CHANGE       FIELD(#A) TO(2)
ENDSELECT
************
RETURN
************
SUBROUTINE   NAME(SUB_BEF)
FETCH        FIELDS(#DATABB) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE
************
SUBROUTINE   NAME(SUB_AFT)
FETCH        FIELDS(#DATABA) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE

 

Example 2: The "Full Blown" way.

Solution Method:

Don’t print the headers and detail on the current page if the total can’t be printed. If the line is to be printed on a new page then (re)print the headers.

Solution Code:
(NOTE: The following code is simplified. Each report will need to be debugged in order to detemine the actual values to use. Also in a general report, the PAG_BEF code will need to split into PAG_BEF and PAG_AFT in order to allow for multiple total lines etc.)
FUNCTION     OPTIONS(*DIRECT)
GROUP_BY     NAME(#FILEA) FIELDS(#KEYA #KEYB #DATA)
DEFINE       FIELD(#NEWPAGE) TYPE(*DEC) LENGTH(15) DECIMALS(0)
             DESC('Force New Page')
DEFINE       FIELD(#OVERFLOW) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC('Page Overflow Line')
DEFINE       FIELD(#PRINTLINE) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC(‘Current Print Line’)
DEF_REPORT   RET_OVERF(#OVERFLOW) RET_LINE(#PRINTLINE)
DEF_HEAD     NAME(#HEAD) FIELDS(#FUNCTION #REP1PAGE
             #KEYA) TRIGGER_BY(#KEYA *OVERFLOW)
DEF_BREAK    NAME(#PAG_BEF) FIELDS(#BLANK)
             TRIGGER_BY(#KEYB) TYPE(*LEADING)
             SUBROUTINE(PAG_BEF)
DEF_BREAK    NAME(#BEFORE) FIELDS(#KEYB #DATABB)
             TRIGGER_BY(#KEYB #NEWPAGE) TYPE(*LEADING)
             SUBROUTINE(SUB_BEF)
DEF_LINE     NAME(#LINE) FIELDS(#KEYA #KEYB #DATA)
DEF_BREAK    NAME(#AFTER) FIELDS(#KEYB #DATABA)
             TRIGGER_BY(#KEYB) TYPE(*TRAILING)
             SUBROUTINE(SUB_AFT)
************
SELECT       FIELDS(#FILEA) FROM_FILE(#FILEA)
CHANGE       FIELD(#A) TO(1)
IF           (#PRINTLINE = #OVERFLOW - 3) /* -3 is footer allowance */
CHANGE       FIELD(#NEWPAGE) TO(‘#NEWPAGE + 1’)
ENDIF
PRINT        LINE(#LINE)
CHANGE       FIELD(#A) TO(2)
ENDSELECT
************
RETURN
************
SUBROUTINE   NAME(SUB_BEF)
FETCH        FIELDS(#DATABB) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE
************
SUBROUTINE   NAME(SUB_AFT)
FETCH        FIELDS(#DATABA) FROM_FILE(FILEB) WITH_KEY(#KEYB)
ENDROUTINE
************
SUBROUTINE   NAME(PAG_BEF)
IF           (#PRINTLINE > #OVERFLOW - 1 - 3 - 3)
             /* -1 is totals allowance */
             /* -3 is group allowance */
             /* -3 is footer allowance */
CHANGE       FIELD(#NEWPAGE) TO(‘#NEWPAGE + 1’)
SKIP         TO_LINE(60)
ENDIF
ENDROUTINE

 

Example 3: The "LANSA" way.

Solution Method:

Put the above code into the "PERFECT" Lansa solution (assuming no one really cares how the problems are solved). Use the power of LANSA rather than forcing RDML. Use alternate solutions where necessary.

Solution Code:
FUNCTION     OPTIONS(*DIRECT)
GROUP_BY     NAME(#FILEA) FIELDS(#KEYA #KEYB #DATA)
DEFINE       FIELD(#NEWPAGE) TYPE(*DEC) LENGTH(15) DECIMALS(0)
             DESC('Force New Page')
DEFINE       FIELD(#OVERFLOW) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC('Page Overflow Line')
DEFINE       FIELD(#PRINTLINE) TYPE(*DEC) LENGTH(3) DECIMALS(0)
             DESC(‘Current Print Line’)
DEF_REPORT   RET_OVERF(#OVERFLOW) RET_LINE(#PRINTLINE)
DEF_HEAD     NAME(#HEAD) FIELDS(#FUNCTION #REP1PAGE
             #KEYA) TRIGGER_BY(#KEYA *OVERFLOW)
DEF_BREAK    NAME(#BEFORE) FIELDS(#KEYB #DATABB)
             TRIGGER_BY(#KEYB #NEWPAGE) TYPE(*LEADING)
DEF_LINE     NAME(#LINE) FIELDS(#KEYA #KEYB #DATA)
DEF_BREAK    NAME(#AFTER) FIELDS(#KEYB #DATABA)
             TRIGGER_BY(#KEYB) TYPE(*TRAILING)
************
SELECT       FIELDS(#FILEA) FROM_FILE(#FILEA)
FETCH        FIELDS(#DATABB #DATABA) FROM_FILE(FILEB)
             WITH_KEY(#KEYB) KEEP_LAST(1)
CHANGE       FIELD(#A) TO(1)
IF           (#PRINTLINE = #OVERFLOW - 3) /* -3 is footer allowance */
CHANGE       FIELD(#NEWPAGE) TO(‘#NEWPAGE + 1’)
ENDIF
PRINT        LINE(#LINE)
CHANGE       FIELD(#A) TO(2)
ENDSELECT
************
RETURN
************

 

Comparison of Examples:

  1. Example 1 is simple and easy to follow and maintain. However, it may leave unneeded header lines at the bottom of a page.
  2. Example 2 is much more complex and difficult to follow and maintain. While it won’t leave orphan headers at the bottom of a page, it will leave extra white space.
  3. Example 3 is the simplest and easiest of the programs to follow and maintain. It is, in fact, more efficient code than either Examples 1 or 2. (A fact that most non-LANSA programmers have a hard time internalising). In fact, it requires one whole I/O less per record processed This would translate to about 25% less processing time. However, it may leave unneeded header lines at the bottom of a page

 

 

 

Click Here!

  Copyright © 1999 & 2000 Glen Ford & Can Da Software div of 1112993 Ontario Inc

Copyright Notices

Home ] Up ]

www,can-da.com
Send mail to webmaster@can-da.com with questions or comments about this web site.
Copyright © 2000 - 2002 Can Da Software div of 1112993 Ontario Inc
Last modified: June 04, 2003

 

Check out our affiliates for great deals:

  $0 Web Hosting   www.geek.com Click Here to Get Free News Headlines on Your Site