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:
- Ignore the level break
- Use brute force
- 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:
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:
- 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?
- When using a *TRAILING Break Subroutine, ensure that all fields to be used in
the subroutine are included in the print line.
- 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.
- When using a *TRAILING Break Subroutine, remember that the subroutine will
not be run the first time through the logic.
- 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.
- 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.
- 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.
- There is no rule saying a print line needs to print anything .... For
example, the command:
DEF_BREAK NAME(#NOTHING)
FIELDS(#BLANK) SPACE_BEF(0)
SPACE_AFT(0)
IDENTIFY(*NOID)
- 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.
- 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.
- 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:
- Example 1 is simple and easy to follow and maintain. However, it may
leave unneeded header lines at the bottom of a page.
- 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.
- 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
