RPG Looks into the Future
February 15, 2006 Ted Holt
If you could write a program that could accurately predict the future, you’d be set for life. Unfortunately–or fortunately, depending on your point of view–such a program is not possible. But you can make a program look ahead as it reads a sequential data set, and that can be a valuable asset. The RPG cycle includes a handy lookahead feature, which allows a program to view data in the next record to be read, but it’s not available for externally described files, nor for full-procedural files. However, rolling your own lookahead logic is easy. To demonstrate just how easy it is, let’s consider a typical read-a-record, write-a-record program that generates a large report. H dftactgrp(*no) actgrp(*new) Fqcustcdt if e disk Fqsysprt o f 132 printer D Save ds likerec(CusRec) D CurrentRec ds likerec(CusRec) D eof s n D LineOut s 132a /free *inlr = *on; exsr read; dow not eof; exsr PrintOneState; enddo; // ================================================== begsr PrintOneState; Save.State = CurrentRec.State; dow not eof and CurrentRec.State = Save.State; LineOut = CurrentRec.STATE + ' ' + %editc(CurrentRec.CUSNUM:'X') + ' ' + CurrentRec.LSTNAM + ' ' + CurrentRec.INIT + ' ' + CurrentRec.STREET + ' ' + CurrentRec.CITY + ' ' + %editc(CurrentRec.ZIPCOD:'X') + ' '; except pline; exsr read; enddo; except spacer; endsr; // ================================================== begsr read; read CusRec CurrentRec; eof = %eof(); endsr; /end-free Oqsysprt e pline 1 O LineOut Oqsysprt e spacer O '=====' Notice the control break on state code. The report, run over a small dataset, looks like this: CA 475938 Doe J W 59 Archer Rd Sutter 95685 ===== CO 389572 Stevens K L 208 Snow Pass Denver 80226 ===== GA 938485 Johnson J A 3 Alpine Way Helen 30545 ===== MN 583990 Abraham M T 392 Mill St Isle 56342 MN 846283 Alison J S 787 Lake Dr Isle 56342 ===== NY 192837 Lee F L 5963 Oak St Hector 14841 NY 839283 Jones B D 21B NW 135 St Clay 13041 NY 397267 Tyron W E 13 Myrtle Dr Hector 14841 ===== TX 938472 Henning G K 4859 Elm Ave Dallas 75217 TX 593029 Williams E D 485 SE 2 Ave Dallas 75218 ===== VT 392859 Vine S S PO Box 79 Broton 05046 ===== WY 693829 Thomas A N 3 Dove Circle Casper 82609 ===== Suppose the people who get this report ask you for a “small” modification. (Does that sound familiar?) They’d like to be able to run the report as it is now, showing all records, but they’d also like to be able to run a summary report, which would take a lot less paper. By summary mode, they mean they only want to see the first record of each state and the separator line would not be needed. Piece of cake, right? But then they ask for one more thing. They’d also like to print a plus sign by the first record for a state if at least one other record for that state exists but is not printed, like this: CA 475938 Doe J W 59 Archer Rd Sutter 95685 CO 389572 Stevens K L 208 Snow Pass Denver 80226 GA 938485 Johnson J A 3 Alpine Way Helen 30545 MN 583990 Abraham M T 392 Mill St Isle 56342 + NY 192837 Lee F L 5963 Oak St Hector 14841 + TX 938472 Henning G K 4859 Elm Ave Dallas 75217 + VT 392859 Vine S S PO Box 79 Broton 05046 WY 693829 Thomas A N 3 Dove Circle Casper 82609 There are probably several messy ways to handle this requirement, but determining whether more records of a group follow the first one would be easy if the program could see into the next record. Well, the program can. The trick is to read one record ahead, or to put it another way, to process not the last record read, but the next-to-last record. Look at the modified source code. H dftactgrp(*no) actgrp(*new) Fqcustcdt if e disk Fqsysprt o f 132 printer D QAD465R pr extpgm('QAD465R') D inSummary n const D QAD465R pi D inSummary n const D Save ds likerec(CusRec) D CurrentRec ds likerec(CusRec) D NextRec ds likerec(CusRec) D eof s n D FirstRead s n inz(*on) D FirstOfState s n inz(*on) D NoNext s n D Mark s 1a D LineOut s 132a /free *inlr = *on; exsr read; dow not eof; exsr PrintOneState; enddo; // ================================================== begsr PrintOneState; Save.State = CurrentRec.State; FirstOfState = *on; dow not eof and CurrentRec.State = Save.State; if inSummary and CurrentRec.State = NextRec.State; Mark = '+'; else; Mark = ' '; endif; if not inSummary or FirstOfState; LineOut = CurrentRec.STATE + ' ' + %editc(CurrentRec.CUSNUM:'X') + ' ' + CurrentRec.LSTNAM + ' ' + CurrentRec.INIT + ' ' + CurrentRec.STREET + ' ' + CurrentRec.CITY + ' ' + %editc(CurrentRec.ZIPCOD:'X') + ' ' + Mark; except pline; endif; FirstOfState = *off; exsr read; enddo; if not inSummary; except spacer; endif; endsr; // ================================================== begsr read; if FirstRead; exsr FetchNext; CurrentRec = NextRec; FirstRead = *off; endif; if NoNext; eof = *on; leavesr; endif; CurrentRec = NextRec; exsr FetchNext; endsr; // ================================================== begsr FetchNext; read CusRec NextRec; NoNext = %eof(); if NoNext; NextRec.State = *hival; endif; endsr; /end-free Oqsysprt e pline 1 O LineOut Oqsysprt e spacer 1 O '=====' The basic logic of the program is unchanged. The biggest difference is in the READ subroutine. READ’s mission is unchanged–it loads the next record to be processed into data structure CurrentRec. But the implementation has been changed so that it also loads the next record into the NextRec data structure. Looking ahead is a simple matter of accessing the NextRec data structure. The first time READ runs, it must fetch two records. The first one goes into the CurrentRec data structure and the second one goes into the NextRec data structure. Each time READ is called thereafter, it copies NextRec into CurrentRec and reads the next record into NextRec. Since READ has to read two records on the first pass, I split the subroutine into two subroutines. Subroutine READ loads the current record, and subroutine READNEXT loads the next record. Notice that I move a dummy high value into state so that the lookahead logic isn’t fooled into thinking that another record exists with the same state value as the last record. I’d also like to point out that the CurrentRec and NextRec data structures are defined using the LIKEREC keyword. Any data structure that is defined with LIKEREC is automatically qualified. That is, its subfields can have the same names as subfields or variables used elsewhere in the procedure. To reference the subfields of a qualified data structure, use the dot notation–data structure name, dot, subfield name. In both versions of the program, I also defined the Save data structure with LIKEDS, which gave me a handy way to properly define save fields for control breaks. To make this program print either the full report or a summary report, I added a parameter, inSummary. It is used to control the printing of the plus sign, the printing of the second and following details lines of a control group, and the separator line. By the way, if you want to play with this technique and want to use the sample dataset, run the following commands on your system. First, run this Create Duplicate Object command from a CL command line, replacing xxx with the name of your library. CRTDUPOBJ OBJ(QCUSTCDT) FROMLIB(QIWS) OBJTYPE(*FILE) TOLIB(xxx) DATA(*NO) Then run this SQL command through Interactive SQL, iSeries Navigator, or some other SQL interface, to load the sorted data into the file you just created. insert into xxx/qcustcdt select * from qiws/qcustcdt order by state, baldue desc |