Control breaks are easily handled in RPG by using the logic cycle built into the compiler. Since the early days of the S/38, however, the trend among RPG programmers has been to bypass the cycle, writing procedural code in the detail calculations to accomplish the same tasks. I'm told that use of the cycle has actually been outlawed in some shops.
If you're a die-hard RPG cycle enthusiast (as I am), please don't stop reading here. It's good to understand other ways of doing things and you may gain a greater appreciation of the RPG cycle. Even if you never write a procedural control break program, you may have to modify or fix one some day.
If you write programs in a language other than RPG, you will find that the principles presented here apply.
If you're not familiar with the concepts of control break processing, see "RPG Control Break Fundamentals," MC, April 1995.
Principles of Procedural Control Breaks
Writing code to process control breaks is not difficult, but there are certain things you, the programmer, must do.
Determine when the value of a control field changes. Save the control field values to compare to the values of the same fields in the next record. When the save field value differs from its related control field value, a control break has occurred.
If your language permits, use the definition of the control fields to define the save variables. This procedure ensures that the save fields match the control fields in type and size. For example, in ILE RPG, you can use the *LIKE DEFINE op code or the LIKE keyword in the definition specifications.
Consider the returned goods data in 1, which is ordered by reason code. To check for a control break on reason, you would use RPG code like that in Label A, 2.
Consider the returned goods data in Figure 1, which is ordered by reason code. To check for a control break on reason, you would use RPG code like that in Label A, Figure 2.
The value saved from the previous control group is compared to the reason code in the current record. If the values do not match, the control break has occurred. Control break calculations are executed, and the current reason code is saved for comparison to subsequent records.
If a break is to occur on the concatenated value of two or more fields, you must check to see if any of them change. An easy way to do this in RPG is to use an ORxx op code. For example, if a date is stored in three fields-year, month, and day-you might use code similar to the code in Label B of 2.
If a break is to occur on the concatenated value of two or more fields, you must check to see if any of them change. An easy way to do this in RPG is to use an ORxx op code. For example, if a date is stored in three fields-year, month, and day-you might use code similar to the code in Label B of Figure 2.
When a control break occurs, process the end of the previous group and the beginning of the new one. Consider the code in Label A, 2. Suppose that SAVRSN has a value of 1 and REASN a value of 2 when this code is executed. The IFNE comparison would prove true; subroutine ENDRSN would perform end of group processing for reason 1, and BGNRSN would perform beginning of group processing for reason 2.
When a control break occurs, process the end of the previous group and the beginning of the new one. Consider the code in Label A, Figure 2. Suppose that SAVRSN has a value of 1 and REASN a value of 2 when this code is executed. The IFNE comparison would prove true; subroutine ENDRSN would perform end of group processing for reason 1, and BGNRSN would perform beginning of group processing for reason 2.
Force control breaks-at the beginning and end of the input file-where there are no records to compare to. The code in 3 shows one method to force control breaks. The initial READ reads the first record in the file and brings the value of the first control group into the program. The EXSR operation that follows forces the program to process the beginning of the first control group. When all records have been read, the last EXSR processes the end of the last control group in the file.
Force control breaks-at the beginning and end of the input file-where there are no records to compare to. The code in Figure 3 shows one method to force control breaks. The initial READ reads the first record in the file and brings the value of the first control group into the program. The EXSR operation that follows forces the program to process the beginning of the first control group. When all records have been read, the last EXSR processes the end of the last control group in the file.
Force control breaks at all subordinate levels-whether or not the values of those subordinate fields have changed. The returned goods data in 1 is sorted by item number within reason code. Reason is the major break field, and item is a minor break field. Because of the break on reason code, there is a control break on item number between records 5 and 6, even though the item number in both records is 109.
Force control breaks at all subordinate levels-whether or not the values of those subordinate fields have changed. The returned goods data in Figure 1 is sorted by item number within reason code. Reason is the major break field, and item is a minor break field. Because of the break on reason code, there is a control break on item number between records 5 and 6, even though the item number in both records is 109.
When the major break field (reason code) changes, you must process a control break on the minor break field (item number)-even if the value of item does not change. Do this by putting control break calculations in their own sub- routines-beginning of group and end of group routines for each level of breaks.
Make the higher level (reason) subroutines execute the lower level (item) subroutines. A beginning of group routine should carry out calculations for its level, then force the beginning of group operations for the control group one level lower. The end of group processing occurs in the opposite order from the beginning of group processing. An end of group routine should force the end of group operations for the control group one level lower before carrying out the calculations for its own level.
If reason is a major break and item number a minor break, the beginning and end of reason subroutines would be like the subroutines in 4. BGNRSN processes the beginning of the reason group, then forces the beginning of an item group (whether item changed or not). ENDRSN forces the end of an item group before processing the end of the reason group.
If reason is a major break and item number a minor break, the beginning and end of reason subroutines would be like the subroutines in Figure 4. BGNRSN processes the beginning of the reason group, then forces the beginning of an item group (whether item changed or not). ENDRSN forces the end of an item group before processing the end of the reason group.
Check for breaks in major to minor order. Your program should check first for the major break. If there is no break at the highest level, it should check the next lower level. If there is no break at that level, it should check for a break one level lower, and so on. If the breaks (in major to minor order) are reason code, item number, and store number, your program will need code similar to 5. This code will work correctly only if each level forces subordinate level breaks.
Check for breaks in major to minor order. Your program should check first for the major break. If there is no break at the highest level, it should check the next lower level. If there is no break at that level, it should check for a break one level lower, and so on. If the breaks (in major to minor order) are reason code, item number, and store number, your program will need code similar to Figure 5. This code will work correctly only if each level forces subordinate level breaks.
Use saved values in end of group routines. When you process the beginning of a control group, the values of the first record of the group are in memory. You can use any field from the input record. When you process the end of a control group, the values in memory are from the first record of the next control group. You must save any values needed from the last record of a control group in your end of group routine.
In 2, Label A for example, if you needed to reference the reason code, you would need to use REASN in the beginning of group routine, but SAVRSN in the end of group routine. If you need other fields from the input record in the end of group routine, you must save them as well.
In Figure 2, Label A for example, if you needed to reference the reason code, you would need to use REASN in the beginning of group routine, but SAVRSN in the end of group routine. If you need other fields from the input record in the end of group routine, you must save them as well.
Using a Template Program
The more programs you write, the more room there is for error. You can reduce the chance of error by working from a good template (or "shell") program based on a solid algorithm. 6 contains an RPG template for control break processing.
The more programs you write, the more room there is for error. You can reduce the chance of error by working from a good template (or "shell") program based on a solid algorithm. Figure 6 contains an RPG template for control break processing.
The comments at the beginning of the program tell what values you'll need to substitute for your application. This template program contains two break levels, but levels can easily be added or deleted. Level 1 (the minor break) is set up for two save fields, so you have a pattern to copy for any level that requires more than one save field.
The comment lines beginning with plus signs are to be replaced with calculations specifically for the application being developed. The lines in Label A define the save fields that are compared to the control fields. Substitute the names of the control fields in factor 2. Duplicate these lines if you need additional control levels, or delete some if you need fewer. If a level has only one break field, you'll need only the SAVExA field for that level. Add a SAVExB field for a second control field on the same level, SAVExC for yet another, and so forth. In the template program at Label A you can see that two control fields are defined for the lowest level break field (SAVE1A and SAVE1B).
Label B is where the program checks for a control break. Again, you may need to add or delete levels.
The subroutines in this template carry out control break processing. One set is for major or intermediate breaks (BGNL02 and ENDL02), and the other is for the lowest level break (BGNL01 and ENDL01). If you have more than two levels, you'll need to duplicate and modify the major break routines.
An Example Application
Figures 7 and 8 contain the source code to process returned goods data with breaks on reason code, item number, and store number. The RPG program RTN006RG was developed directly from the template program. Let me point out a couple of details.
In this program, I added a logical variable called EMPTY at point A. This value is (true) if the input file is empty and (false) if the file has at least one record. I check this variable at beginning and end of job; if there are no records in the file, I skip control break processing. This process prevents control headings and totals from printing when there is no data. If you want your control break programs to work the same way, you can add this code to the template.
In the page overflow routine (section B), I added calculations to print page headings and set the detail line for group indication.
8 shows partial DDS for the printer file. I've omitted some of the DDS because of space constraints. You can see the report in 9.
Figure 8 shows partial DDS for the printer file. I've omitted some of the DDS because of space constraints. You can see the report in Figure 9.
Advantages of Procedural Break Processing
If you're accustomed to using the RPG cycle for control break processing, you may be wondering why you'd ever want to use the procedural approach. One reason is that procedural processing can save you from having to create additional objects when you need to break on calculated fields.
For example, suppose a payroll report requires subtotals for three groups of people: those who worked less than 40 hours, those who worked exactly 40 hours, and those who worked more than 40 hours. How would you do that with cycle programming?
You could run a program to build a work file, which would contain the fields needed by the control break program, plus an extra field into which you would place a code, perhaps 1 for less than 40 hours, 2 for exactly 40, and 3 for more than 40. This process requires building and reading an extra work file, developing an extra program to build the file, and spending extra processing time running the program.
Procedural code eliminates the need for the extra file and program. You precede the control break process with the calculations needed to build a variable according to the number of hours worked and check that status variable for a break. (Of course, the file would still need to be in order by the number of hours.) 10 illustrates a control break on a calculated field.
Procedural code eliminates the need for the extra file and program. You precede the control break process with the calculations needed to build a variable according to the number of hours worked and check that status variable for a break. (Of course, the file would still need to be in order by the number of hours.) Figure 10 illustrates a control break on a calculated field.
A less important advantage is that you can have more break levels. The RPG cycle allows only nine levels of control breaks (which is usually more than enough), but the procedural method allows you to have as many levels as you wish.
Give Yourself a Break
If you are an RPG programmer who uses the cycle for control break programs, let me encourage you to learn to write control break programs without the cycle. A master of any trade knows many different ways to do the same thing.
Ted Holt is an associate technical editor for Midrange Computing.
The Control Break Saga Continues
Figure 1 Returned Goods Data
Reason Trans. Trans. Qty. Unit returned Item Store Number Date Returned Price 1 101 BAT 78333 12/02/94 4 $10.00 1 101 BAT 78338 12/02/94 2 10.00 1 101 VIC 78363 12/02/94 1 10.50 1 103 BAT 78327 12/02/94 50 .80 1 109 VIC 78315 12/01/94 1 9.50 2 109 BAT 78319 12/01/94 500 1.50 2 109 NAT 78314 12/01/94 75 1.25 2 109 OXF 78348 12/02/94 250 1.25 2 109 OXF 78388 12/03/94 300 1.50 2 114 NAT 78321 12/02/94 100 .25 2 114 NAT 78392 12/03/94 50 .25 3 109 NAT 78311 12/01/94 250 1.50
The Control Break Saga Continues
Figure 2 Sample Control Break Processing
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 C SAVRSN IFNE REASN C EXSR ENDRSN C EXSR BGNRSN C MOVE REASN SAVRSN C *LIKE DEFN REASN SAVRSN C ENDIF * C SAVYR IFNE YEAR C SAVMON ORNE MONTH C SAVDAY ORNE DAY *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
The Control Break Saga Continues
Figure 3 Forcing the Highest Level Break at Beginning and E
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 * initial read C READ INPUT 11 * force beginning of group calcs for first reason code C EXSR BGNRSN C *IN11 DOWEQ*OFF *... calcs to process all records in the file go here C READ INPUT 11 C ENDDO * at this point, end of file has been reached * force end of group calcs for last reason code C EXSR ENDRSN *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
The Control Break Saga Continues
Figure 4 Subroutines to Process Control Groups
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 C BGNRSN BEGSR * C MOVE REASN SAVRSN *... (calculations for beginning of reason group) C EXSR BGNITM * C ENDSR *********** C ENDRSN BEGSR * C EXSR ENDITM *... (calculations for end of reason code) C ENDSR *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
The Control Break Saga Continues
Figure 5 Checking for Multiple-level Control Breaks
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 C SELEC C REASN WHNE SAVRSN *... (process major control break) C ITEM# WHNE SAVITM *... (process intermediate control break) C STORE# WHNE SAVSTO *... (process minor control break) C ENDSL *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
The Control Break Saga Continues
Figure 6 RPG Template Program for Procedural Control Breaks
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 * Shell for procedural control break programs * * Replace "filename" with name of file to read. * Replace "recordname" with name of record in file. * Replace "printfil" with name of report file. * Replace "ctl**" fields with names of control break fields. * Replace "hl" with highest break level (2 digits). * FfilenameIF E K DISK F recordname KRENAMERECIN FprintfilO E 88 PRINTER * Define save fields C *LIKE DEFN ctl2a SAVE2A C *LIKE DEFN ctl1a SAVE1A C *LIKE DEFN ctl1b SAVE1B *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 * *+++++++ Insert first-time calculations * C READ RECIN 11 C MOVE *IN11 EOF 1 * Force break for highest level C EXSR BGNLhl * Begin main processing loop C EOF DOWEQ'0' * Check for control break C SELEC C ctl2a WHNE SAVE2A C EXSR ENDL02 C EXSR BGNL02 C ctl1a WHNE SAVE1A C ctl1b ORNE SAVE1B C EXSR ENDL01 C EXSR BGNL01 C ENDSL * Check for page overflow C *IN88 IFEQ *ON * *+++++++ Insert page overflow calculations * C MOVE *OFF *IN88 C ENDIF * *+++++++ Insert detail processing here * C READ RECIN 11 C MOVE *IN11 EOF C ENDDO * End of file reached * Force break for highest level C EXSR ENDLhl * *+++++++ Insert end-of-job calcs * C MOVE *ON *INLR *********** C BGNL02 BEGSR * C MOVE ctl2a SAVE2A * *+++++++ Insert calculations for beginning of level-2 group * C EXSR BGNL01 * C ENDSR *********** C ENDL02 BEGSR * C EXSR ENDL01 * *+++++++ Insert calculations for end of level-2 group * C ENDSR *********** C BGNL01 BEGSR * C MOVE ctl1a SAVE1A C MOVE ctl1b SAVE1B * *+++++++ Insert calculations for beginning of level-1 group * C ENDSR *********** C ENDL01 BEGSR * *+++++++ Insert calculations for end of level-1 group * C ENDSR *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
The Control Break Saga Continues
Figure 7 Program RTN006RG
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 * Returned goods report by reason * * Indicator summary * 71 - Group indicate reason * 72 - Group indicate item * 73 - Group indicate store FRTN002PFIF E K DISK F RTNS KRENAMERECIN FITM001PFIF E K DISK FRTN006P1O E 88 PRINTER E RC 1 3 3 0 RD 15 * Define save fields C *LIKE DEFN REASN SAVE3A C *LIKE DEFN ITEM# SAVE2A C *LIKE DEFN STORE# SAVE1A * Do 1st time only calcs C WRITEPAGEHDR * C READ RECIN 11 C MOVE *IN11 EOF 1 C MOVE *IN11 EMPTY 1 Empty file? * Force break for highest level C EMPTY IFEQ '0' C EXSR BGNL03 C ENDIF * Begin main processing loop C EOF DOWEQ'0' * Check for control break C SELEC C REASN WHNE SAVE3A C EXSR ENDL03 C EXSR BGNL03 C ITEM# WHNE SAVE2A C EXSR ENDL02 C EXSR BGNL02 C STORE# WHNE SAVE1A C EXSR ENDL01 C EXSR BGNL01 C ENDSL * Check for page overflow C *IN88 IFEQ *ON C WRITEPAGEHDR C MOVE *OFF *IN88 * turn on group indication C MOVE *ON *IN71 C MOVE *ON *IN72 C MOVE *ON *IN73 C ENDIF * C QTY MULT PRICE XAMT C ADD QTY P@QTS C ADD XAMT P@XAS * C WRITEDTLLINE * turn off group indication C MOVE *OFF *IN71 C MOVE *OFF *IN72 C MOVE *OFF *IN73 * C READ RECIN 11 C MOVE *IN11 EOF C ENDDO * End of file reached * Force break for highest level C EMPTY IFEQ '0' C EXSR ENDL03 C WRITEGRDTOTAL C ENDIF C WRITEEOJLINE C MOVE *ON *INLR *********** C BGNL03 BEGSR * C MOVE REASN SAVE3A C MOVE *ZERO P@QTR C MOVE *ZERO P@XAR C Z-ADD1 RX 30 C REASN LOKUPRC,RX 91 C *IN91 IFEQ *ON C MOVELRD,RX REASND C ELSE C MOVE *BLANKS REASND C ENDIF C MOVE *ON *IN71 C EXSR BGNL02 * C ENDSR *********** C ENDL03 BEGSR * C EXSR ENDL02 C MOVE SAVE3A P@REAS C ADD P@QTR P@QTG C ADD P@XAR P@XAG C WRITERSNTOTAL * C ENDSR *********** C BGNL02 BEGSR * C MOVE ITEM# SAVE2A C MOVE *ZERO P@QTI C MOVE *ZERO P@XAI C ITEM# CHAINITEM 91 C *IN91 IFEQ *ON C MOVE *ALL'?' ITEMDS C ENDIF C MOVE *ON *IN72 C EXSR BGNL01 * C ENDSR *********** C ENDL02 BEGSR * C EXSR ENDL01 C MOVE SAVE2A P@ITEM C ADD P@QTI P@QTR C ADD P@XAI P@XAR C WRITEITMTOTAL * C ENDSR *********** C BGNL01 BEGSR * C MOVE STORE# SAVE1A C MOVE *ZERO P@QTS C MOVE *ZERO P@XAS C MOVE *ON *IN73 * C ENDSR *********** C ENDL01 BEGSR * C MOVE SAVE1A P@STOR C ADD P@QTS P@QTI C ADD P@XAS P@XAI C WRITESTRTOTAL * C ENDSR ** Reasons 001Broken/damaged 002Not as expected 003Other
The Control Break Saga Continues
Figure 8 Printer File RTN006P1
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 A REF(RTN002PF) A R PAGEHDR SKIPB(4) * ... (fields for page headings) A R DTLLINE SPACEB(1) A 71 REASN R 1 A 71 REASND 10 5 A 72 ITEM# R 21 A 72 ITEMDS R 25REFFLD(ITEMDS ITM001PF) A 73 STORE# R 36 A XACT# R 42 A XACTDT R 50 A QTY R 61EDTCDE(4) A PRICE R 68 A XAMT R +3 82REFFLD(PRICE) EDTCDE(4) A R STRTOTAL SPACEB(2) SPACEA(1) A 42'Total store' A P@STOR R +1REFFLD(STORE#) A P@QTS R +1 60REFFLD(QTY) EDTCDE(4) A P@XAS R +1 81REFFLD(XAMT *SRC) EDTCDE(4) A R ITMTOTAL SPACEB(1) SPACEA(1) A 42'Total item' A P@ITEM R +1REFFLD(ITEM#) A P@QTI R +1 60REFFLD(QTY) EDTCDE(4) A P@XAI R +1 81REFFLD(XAMT *SRC) EDTCDE(4) A R RSNTOTAL SPACEB(1) SPACEA(1) A 42'Total reason' A P@REAS R +1REFFLD(REASN) A P@QTR R +1 60REFFLD(QTY) EDTCDE(4) A P@XAR R +1 81REFFLD(XAMT *SRC) EDTCDE(4) A R GRDTOTAL SPACEB(1) A 42'Grand totals' A P@QTG R +1 60REFFLD(QTY) EDTCDE(4) A P@XAG R +1 81REFFLD(XAMT *SRC) EDTCDE(4) A R EOJLINE SPACEB(2) A 1'** End of report **'
The Control Break Saga Continues
Figure 9 Report Produced by Program RTN006RG
3/09/95 Returned Goods Page 1 Store --Transaction--- Unit Extended ------Reason------- -----Item----- nbr nbr date Qty price price 1 Broken/damaged 101 BROOM BAT 78333 4 10.00 40.00 78338 2 10.00 20.00 Total store BAT 6 60.00 VIC 78363 1 10.50 10.50 Total store VIC 1 10.50 Total item 101 7 70.50 103 DUSTER BAT 78327 50 .80 40.00 Total store BAT 50 40.00 Total item 103 50 40.00 109 TOWELS VIC 78315 1 9.50 9.50 Total store VIC 1 9.50 Total item 109 1 9.50 Total reason 1 58 120.00 2 Not as expected 109 TOWELS BAT 78319 500 1.50 750.00 Total store BAT 500 750.00 NAT 78314 75 1.25 93.75 Total store NAT 75 93.75 OXF 78348 250 1.25 312.50 78388 300 1.50 450.00 Total store OXF 550 762.50 Total item 109 1125 1606.25 114 BAGS NAT 78321 100 .25 25.00 78392 50 .25 12.50 Total store NAT 150 37.50 Total item 114 150 37.50 Total reason 2 1275 1643.75 3 Other 109 TOWELS NAT 78311 250 1.50 375.00 Total store NAT 250 375.00 Total item 109 250 375.00 Total reason 3 250 375.00 Grand totals 1583 2138.75 ** End of report **
The Control Break Saga Continues
Figure 10 Control Break on a Calculated Field
*. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 * build control variable C SELEC C HOURS WHLT 40 C MOVE '1' OTFLAG 1 C HOURS WHEQ 40 C MOVE '2' OTFLAG C OTHER C MOVE '3' OTFLAG C ENDSL * control break routine C OTSAVE IFNE OTFLAG ... control break calcs go here * save the value of the control field C MOVE OTFLAG OTSAVE C *LIKE DEFN OTFLAG OTSAVE C ENDIF *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6
LATEST COMMENTS
MC Press Online