02
Sat, Nov
2 New Articles

TechTip: An Easier Way to Process Level Breaks

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times
Usually, when writing a program with level-break processing--be it a report, batch data maintenance, or whatever--the same approach is used. That approach is to read the input file straight through and compare the key field values to their values from the previous record. This requires holding fields for the previous record's values. It also requires knowing whether your current record is the first record (so as not to process the previous key values) or the last record (so as not to process the next key values). This process can be quite messy, especially if it involves more than one file.

I would like to introduce a different method that I learned several years ago. I have found that this approach makes level-break processing much easier and more intuitive.

The approach involves breaking a level break down into three distinct steps:
1) Prepare for the level break (by clearing out the totals for the break, setting up the page to break, loading header data, etc.).
2) Process the records for the break.
3) End the break (by printing the total line for this break, adding those totals to the next higher break's totals, etc.).

The key to the simplicity of this method is step 2. What is meant by "Process the records for the break"? This is where you process the detail level of your report or process, or it is where you perform the next lower-level break. So, in this approach, each level break is nested in the next higher break. It is coded as a group of nested DO loops.

If you write such a control break program in RPG, using its native file access opcodes, you do not even need hold fields.

For this example, suppose you have a veterinary clinic that wishes to print a monthly invoice history. They wish to print the details of each invoice and then the total quantity and billed amount for each invoice. They want to group the invoices first by customer, then by doctor, printing the total quantity and billed amount at each break, and finally ending with the grand totals. So the level breaks for this report are:
1) Grand Total
2) Doctor (DRNUM)
3) Customer (CSTNUM)
4) Invoice Number (INVNUM)

Assume that this information is in a file INVHDRL1, which is keyed by Doctor, Customer, and Invoice Number. A file INVDTL contains the invoice details, keyed by unique Invoice Number.

The RPG program will of course have these files coded, with an external print file P_INVHST using indicator 70 as the overflow indicator. Assign *IN70 to the name NewPage (either through INDDS or a DS pointing to *IN).

Now, to implement this level-break approach, you first need a set of KLISTs, one for each break level:

C     K1            klist
C                   kfld                    DRNUM
C
C     K2            klist
C                   kfld                    DRNUM
C                   kfld                    CSTNUM
C
C     K3            klist
C                   kfld                    DRNUM
C                   kfld                    CSTNUM
C                   kfld                    INVNUM


Or, for V5R2:

D BreakLevel    E DS                  EXTNAME(INVHDRL1:*KEY)


Here is how the basic code would look:

 /free
  
  exsr $Main;
   
  *inlr = *on;

 //=================================================================  
  begsr $Main;
 // This would be the Grand Total Level
 //=================================================================
    // Step 1
    clear t0qty;                    // Initialize Grand Total fields
    clear t0amt;

    NewPage = *on; // Force page break

    read INVHDRL1;                  // The traditional priming read

    // Step 2
    dow not %eof;                   // Doctor
exsr $Level1;
      read INVHDRL1;
    enddo;

    // Step 3
    exsr $NewPage;
    write Total0; // Write Grand Total line

  endsr; 
                              
 //=================================================================  
  begsr $Level1;
 // This would be the Doctor Level
 //=================================================================
    // Step 1
    clear t1qty;                    // Initialize Doctor Total fields
    clear t1amt;

    chain(e) DRNUM DRMAST;    // Let's get the Doctor's name and
    if %error; // put it on the header.
      DRNAME = '*** Dr. not found';
    endif;

    NewPage = *on; // Force page break

    // Step 2
    dow not %eof;                   // Customer
exsr $Level2;
      reade K1 INVHDRL1; // V5R2: reade %kds(BreakLevel:1) INVHDRL1
    enddo;

    // Step 3
    exsr $NewPage;
    write Total1; // Write Doctor Total line

    t0qty = t0qty + t1qty; // Add to next higher break's totals
    t0amt = t0amt + t1amt; // V5R2: t0qty += t1qty; t0amt += t1amt;

    setgt K1 INVHDRL1; // Point to next doctor for next execution
// of this level break.  THIS IS 
// IMPORTANT!
// V5R2: setgt %kds(BreakLevel:1) INVHDRL1

  endsr;

 //=================================================================  
  begsr $Level2;
 // This would be the Customer Level
 //=================================================================
    // Step 1
    clear t2qty;                    // Initialize Customer Total fields
    clear t2amt;

    chain(e) CSTNUM CSTMAST;  // Let's get the Customer's name and
    if %error; // put it on the header.
      CSTRNAME = '*** Customer not found';
    endif;

    NewPage = *on; // Force page break

    // Step 2
    dow not %eof;                   // Invoice
exsr $Level3;
      reade K2 INVHDRL1; // V5R2: reade %kds(BreakLevel:2) INVHDRL1
    enddo;

    // Step 3
    exsr $NewPage;
    write Total2; // Write Customer Total line

    t1qty = t1qty + t2qty; // Add to next higher break's totals
    t1amt = t1amt + t2amt; // V5R2: t1qty += t2qty; t1amt += t2amt;

    setgt K2 INVHDRL1; // Point to next doctor/customer for 
// next execution of this level break.  
// THIS IS IMPORTANT!
// V5R2: setgt %kds(BreakLevel:2) INVHDRL1

  endsr; 

 //=================================================================  
  begsr $Level3;
 // This would be the Invoice Level
 //=================================================================
    // Step 1
    clear t3qty;                    // Initialize Invoice Total fields
    clear t3amt;

    write Invheader; // We will write a special line to
// head the invoice, showing invoice
// number, date, etc.  Don't need a 
// page break, though.
    

    // Step 2
    setll INVNUM INVDTL; // We will shift to a different file for
    reade INVNUM INVDTL; // the details.  See how easy it is with
// this level break approach?
    dow not %eof;                   // Details!
exsr $Detail;
      reade INVNUM INVDTL;
    enddo;

    // Step 3
    exsr $NewPage;
    write Total3; // Write Invoice Total line

    t2qty = t2qty + t3qty; // Add to next higher break's totals
    t2amt = t2amt + t3amt; // V5R2: t2qty += t3qty; t2amt += t3amt;

    setgt K3 INVHDRL1; // Point to next doctor/customer/invoice 
// for next execution of this level break. // THIS IS IMPORTANT!
// V5R2: setgt %kds(BreakLevel:3) INVHDRL1

  endsr;

 //=================================================================  
  begsr $Detail;
 // This would be where the details are printed.  Since this is not a 
 // level break, we won't follow the Step 1/2/3.
 //=================================================================
    // Here is where we construct the detail report line

    exsr $NewPage;
    write Detail; // Write Detail line

    t3qty = t3qty + INVDQTY; // Add to next higher break's totals
    t3amt = t3amt + INVDAMT; // In this case, it is the lowest break
     // V5R2: t3qty += INVDQTY; 
//       t3amt += INVDAMT;

// You have the option of adding the 
// detail to ALL of the levels' totals,
// here in the detail processing, instead 
// of the appropriate Step 3s.  There
// may be cases where that is necessary,
// but usually it's not, and it is more 
// processor intensive.

  endsr;

 //=================================================================  
  begsr $NewPage; 
 //=================================================================
    if NewPage;
      write Header;
      NewPage = *off;
    endif;

  endsr;


You can see that this code is easy to follow and easy to maintain. Now, suppose you want to add an Average Amount Billed per Invoice to the Doctor total line. Easy enough.

In step 1 of the Doctor break, clear the fields:

clear t1count;
clear t1avg;

In step 3 of the Doctor break, just before you print the total line, calculate the average:

monitor;
  t1avg = t1amt / t1count;
on-error;
  t1avg = *zero;
endmon;


Since you need to count invoices for the doctor, count each invoice at step 3 of the Invoice break:

t1count = t1count + 1;

The intuitive nature of this level-break approach makes changes like this easy.

As I said, using RPG's native file access opcodes in this approach makes hold fields unnecessary. But if you get your data some other way, you will need some simple hold fields. For example, what if you want to use this approach with data from an SQL cursor? You wouldn't use KLISTs; you would do something like this instead:

DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++
D SQLEOF          S               N
D
D SQLRow          DS
D   KeyField1       a      b      t Key fields must be contiguous!
D   KeyFieldn       c      d      t
D   DataFieldA ...
D   DataFieldB ...
D   DataFieldC ...
D SQLPtr          S               *   INZ(%ADDR(SQLRow))
D RowKeys         DS                  BASED(SQLPtr)
D   K1              a      b      A              This spans the first key
D   Kn              a      d      A              This spans the first n keys
D HoldKeys        DS
D   H1                                LIKE(K1)
D   Hn                                LIKE(Kn) 


The only changes to the code above are in the $Main routine, the level break routines, and the level break routine calling the detail processing:

In the $Main routine:

 //=================================================================  
  begsr $Main;
 //=================================================================
    // Step 1
     .
.
.

    exsr $Read;                    // The traditional priming read

    // Step 2
    dow not SQLEOF;                 // No READ here!
exsr $Level1;
    enddo;

    // Step 3
     .
.
.

  endsr; 


In the level break routines:

 //=================================================================  
  begsr $Level1;
 //=================================================================
    // Step 1
.
.
.

    // Step 2
    H1 = K1;
    dow not SQLEOF and (K1 = H1);
exsr $Level2; // No READE!
    enddo;

    // Step 3
.
.
. // no SETGT!

  endsr;


In the level break routine calling the detail processing:

 //=================================================================  
  begsr $Leveln;
 //=================================================================
    // Step 1
     .
.
.
    

    // Step 2
    Hn = Kn;
    dow not SQLEOF and (Kn = Hn);   // Details!
exsr $Detail;
      exsr $Read; // This is the only other read!
    enddo;

    // Step 3
.
.
. // No SETGT!

  endsr;


 //=================================================================  
  begsr $Read; 
 //=================================================================
    /exec sql  
       fetch CsrName into :SQLRow
    /end-exec

    // Because of the pointer, K1 through Kn are loaded automatically

    SQLEOF = (SQLCOD <> *zero);

  endsr;


As you can see, the code changes are minimal; hold field usage is straightforward.

A similar approach can be used in COBOL. If you do not or cannot use COBOL pointers, you can set up your "KLISTs" like so:

01 K1.
   05    KEY-FIELD-1 ...

01 Kn.
   05    KEY-FIELD-1 ...
             .
             .
   05    KEY-FIELD-n ...

01 HOLD-FIELDS.
   05    H1 LIKE K1.
         .
         .
   05    Hn LIKE Kn.

The read routine would then have one MOVE CORR from the input record to each Kx structure.

This alternative way of coding level break processing splits a level break into three parts and nests them in their proper hierarchy, which makes level breaks intuitive, simple to code (faster, too), and easy to maintain and enhance. This method allows for multiple files and does not require concern for the first or last record. Give it a try!

Doug Eckersley is an iSeries application developer in Columbus. He has a decade of experience with the AS/400 and is certified by IBM. He also co-authored Brainbench's RPG IV certification exam. He can be reached by email at This email address is being protected from spambots. You need JavaScript enabled to view it..

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: