9.3 A Simple Report

With the three basic steps in report design understood, we can proceed to writing report programs. Our first example is a very simple report: a list of subscriber codes and subscriber names in no particular order. This completes the first step in the report design process.

All of the information pertaining to subscribers is kept in the file sub. The fields needed are named subscriber and name. Subscriber is a string field of length 15 printable characters. Name is also a string field, its length is 35 printable characters. This information completes step two.

To simplify this example, page headings and footings are not used. Since summary information is not required, that part of the layout can also be skipped. The only thing left is the report detail. For a first try, print the subscriber code starting in column 1, and the name starting in column 20. This leaves at least five blank columns between the fields. This layout completes step three of the report design process.

You are now ready to start writing the report program. As stated in Chapter 10, Report Writer Programming Reference, you must first declare the files to be used. The only file you are going to be using is sub. Give this file an internal name of subscribers. The way this is done in the Report Writer is:

FILE subscribers IS "sub"

Any reference you make to subscribers is interpreted by the Report Writer to mean the RMSfile sub.

Next you must declare the fields in each of the files that you have declared above. Again you are only using one file, the fields in this file are subscriber, and name. This declaration is made in a report program as:

FIELDS IN subscribers ARE

subscriber, name;

Report programs are free format so the previous declaration could also look like:

FIELDS IN subscribers ARE

subscriber,

name;

The MAIN procedure comes next and should show what the main process of the report program is. In this example that process is:

Read a record

Repeat until end of file is reached:

Print out the subscriber information

Read next record.

Reading a set of records is done with the FOR statement. This statement reads each record in a file, executing a statement after each record is read. For this example, you want to print out the subscriber information for each subscriber record. For this you need the PRINT statement. With this statement you can print out the value of any field, skip over to a given column, or start printing on the next line.

The subscriber code is to be printed starting in column 1. Every time the Report Writer starts printing on a new line, the output is started in column 1. Therefore, the subscriber code can be printed at the start of a new line. After printing the subscriber code, skip over to column 20 to print the subscriber name. To do this in the PRINT statement, use the TAB function. TAB looks like:

TAB (n)

where n is the column you want to skip to. If you are in column 10

PRINT TAB (20)

positions you at column 20.

NOTE: If you are in column 35 (or any column greater than 20) and try to PRINT TAB (20), all you will get is a single blank. Since we know that the subscriber code is less than 16 characters, to skip over to column 20 you would write:

TAB (20)

To start the next subscriber on a new line, include the NL function, that skips down to the start of the next line. Without this function, all of the output is printed across the page. We can now present the MAIN procedure of the report:

MAIN

FOR EACH subscribers

PRINT subscriber, TAB (20), name, NL;

This procedure reads through the entire file of subscribers and for each one read, it prints out the information for one subscriber. The entire program looks like:

FILE subscribers IS "sub"

FIELDS IN subscribers ARE

subscriber, name;

MAIN

FOR EACH subscribers

PRINT subscriber, TAB (20), name, NL;

Suppose you were to pick up this program 3 months from now. Would you understand what the program did and why it was around? Certainly for something this small you would argue that you would; however, if this program was much larger, you would probably need something to help jog your memory. One means of identifying reports can be done with comments. In the Report Writer, a comment has the form:

/*...anything you want...*/

The Report Writer ignores everything put between the /* and */. If you want, comments can even extend over multiple lines:

/*

One comment

spread over

several lines.

*/

You must be careful to always put the ending */ at the end of your comment. Otherwise, parts of your program will be ignored by the compiler. For the first program, put a simple comment at the beginning stating what the program does.

/* list subscribers */

FILE subscribers IS "sub"

FIELDS IN subscribers ARE

subscriber, name;

MAIN

FOR EACH subscribers

PRINT subscriber, TAB (20), name, NL;

While this report program might be simple and easy to understand, it is not too likely that anyone would have much use for such a report. A more common request would add a few requirements:

Print a list of subscribers showing the full address.

Include page headings to identify the report.

Print the date the report was run to help maintain the printouts.

Upon further investigation you might find out that the output should be sorted by the subscriber code, and the address is to be printed in mailing label form under the subscriber name.

Let us start with printing the full address. The first step is to change the FIELD list for the file to include the full address. These fields are:

address city

state zip

The new FIELD declaration might look like:

FIELDS IN subscribers ARE

subscriber, name,

address, city,

state, zip;

The next thing is to change the PRINT statement. A first attempt might be to make the change as:

PRINT subscriber, TAB (20), name, NL,

TAB (20), address [1], NL,

TAB (20), address [2], NL,

TAB (20), city, ", ", state, " ", zip, NL;

This is a good start, but what happens when address [2] is empty? In this case a blank line is printed. This makes for very confusing output. The solution is to check to see if address [2] is empty, if something is there then print it, otherwise don't print it or the new-line following it. This kind of decision making by your program requires the IF statement. This statement has the following form:

IF condition THEN statement1 ELSE statement2

The condition we want to test is when the second address string is not empty. This condition is expressed as:

address [2] NE ""

The double quotes ("") denote an empty string. The NE symbol is short for not equal to. When this condition is true, we want to print out the second address; when the condition is false, we don't want to do anything so we can omit the ELSE clause. This solves having one blank line in the middle of one subscriber. However, now all of the subscribers will run together. This was all right when each subscriber consisted of only one line, but now it might be nicer to print a blank line after each subscriber. This can be done by printing two NL's. The code to correctly print out the subscriber information might look like:

PRINT subscriber, TAB (20), name, NL,

TAB (20), address [1], NL;

IF address [2] NE "" THEN

PRINT address [2], NL;

PRINT TAB (20), city, ", ", state, " " , zip, NL, NL;

Notice that what was one statement is now three. The syntax of the FOR statement only allows us to put one statement following the file name. In order to put more than one statement where only one is allowed by the syntax, you have to use a compound statement. In a compound statement, you can put as many statements as you want between the compound statement markers. Markers can be either braces ({ }) or the words BEGIN and END. In our examples we will always use the BEGIN-END combination for compound statements. The new MAIN procedure might look like:

MAIN

FOR EACH subscribers BEGIN

PRINT subscriber, TAB (20), name, NL,

TAB (20), address [1], NL;

IF address [2] NE "" THEN

TAB (20), PRINT address [2], NL;

PRINT TAB(20), city, ", ", state,

" ", zip, NL, NL;

END

Notice how cluttered this procedure has become. It is difficult to see what the main process of the report is. It would be easier to read if the details of printing the subscriber information were moved somewhere else, leaving only an indication of when they should be printed. This separation of code is accomplished through procedures.

Procedures are sections of code that accomplish some task and have been given a unique name by you, the programmer. We have already seen one use of procedures in the special MAIN procedure. This procedure has the task of generating the report. We have now identified another task to be done: print out the information for one subscriber.

You must identify what is to be done by the procedure and decide on a name to identify the procedure. It is helpful if the name of the procedure suggests the purpose of the procedure. In this example, we need a procedure to print all of the information for one subscriber. Some good names for this procedure might be:

print_subscrib

a_subscriber

details

We have chosen details only because we have many reports where the procedures do one of three things: print headings, print details, and print summary. You can place procedures before or after the MAIN procedure. As a convention, we prefer to have the MAIN procedure come before the other procedures, but you are free to establish your own conventions so long as you satisfy the rules given in Chapter 10, Report Writer Programming Reference. Now what you need is some way of executing this procedure from within the MAIN procedure.

Executing one procedure from within another procedure is done by a procedure call. A procedure call in the Report Writer looks like

DO procname;

This directs the Report Writer to remember where it is in the current procedure and to begin execution in the procedure whose name is procname. After the end of the called procedure is reached, execution resumes at the remembered spot in the calling procedure. As an example, to call the procedure details we just defined, write the statement

DO details;

With this new procedure call, the MAIN procedure is now reduced to:

MAIN

FOR EACH subscribers

DO details;

You are now ready to add sorting to the report program. Recall that the report needs to be sorted by the subscriber code. Sorting requires an additional statement: The SELECT statement.

The SELECT statement does more than just sorting, but for now that is all we need. What the SELECT statement does is prepare a file so that the records can be processed in a certain order by the FOR statement. The actual RMSfile remains unchanged, the Report Writer simply accesses the records in a different order. What we want to do is to direct the Report Writer to access the records so that the subscriber field appears in ascending order. This can be done in a report program as:

SELECT FROM subscribers

SORTED BY subscriber;

This causes the next FOR statement to read the records in such a way that the subscriber records appear in ascending order.

The place to put the SELECT statement in this example is in the MAIN procedure right before the FOR statement. The syntax for the MAIN procedure is:

MAIN statement

Once again we are faced with having to put more than one statement where only one is permitted by the syntax. Since this is a very common problem in all procedures, we always make the statement of our procedures a compound statement, even if there is only one statement in the procedure. Thus the MAIN procedure might look like:

MAIN

BEGIN

SELECT FROM subscribers

SORTED BY subscribers;

FOR EACH subscribers

DO details;

END

The last requirement is to add page headings. Since page headings are a common requirement of reports, there is a special feature to make printing headings very simple. If you ran the first example report, you may have noticed that it printed several blank lines at various times. This is because the Report Writer is set up to print on standard continuous paper. The page headings are printed a certain number of lines from the top of the page, and the page footings are printed within a certain number of lines from the bottom of the page. After page footings are printed, the Report Writer advances the paper past the perforation to the line where the headings start. The exact amount of spacing done is determined when the Report Writer is configured for your system.

Each time the Report Writer spaces to the line where headings are supposed to start, it signals a TOP OF PAGE event. Events tell your program when something special has happened. Your report program can watch for these events with the break statement. A break statement defines what event to look for and what is to be done when the event occurs. If your report program sets a program break for the TOP OF PAGE event, the specified statement is executed when the Report Writer spaces to the page heading line of each page. This statement should print out the necessary headings. In summary, the problem of printing page headings is divided into two steps:

1. provide the statements to print headings

1. set a break for the TOP OF PAGE event to execute the statements from step 1.

The requirements specified for page headings were that there be some type of identification for the report (a title), the date the report was generated, and general headings to identify the information printed. The current date can be shown by printing the parameter $todays_date. The following PRINT statements can be used to print the page headings:

PRINT TAB (20), "S u b s c r i b e r L i s t",

TAB (60), $todays_date, NL;

PRINT "Subscriber", TAB (20), "Name and Address",

NL, NL;

Two things need to be noted here: these statements can be combined and used in the break statement; and another task, that of printing page headings, has just been defined. The definition of a new task suggests a new procedure which we choose to call headings. This procedure looks like:

PROCEDURE headings /* print page headings */

BEGIN

PRINT TAB (20), "S u b s c r i b e r L i s t",

TAB (60), $todays_date, TAB (70), NL;

PRINT "Subscriber", TAB (20), "Name and Address",

NL, NL;

END

Now, it is quite easy to find where the page headings get printed. Again, this procedure is put after the MAIN procedure. We are now ready to tell the Report Writer what to do when the TOP OF PAGE event occurs.

The break statement controls what is done when an event occurs. One form of the break statement is:

AT event statement

This form controls those events associated with the output from the Report Writer. The event we are interested in is the TOP OF PAGE event. Statement can be any valid Report Writer statement (including a compound statement). You could put in the PRINT statements given above; however, since we want to put the break statement the in MAIN procedure, we do not want anything that complex here. Instead, a procedure call to headings is our statement. Setting the break must be done before any output, so typically all of the breaks appear at the beginning of the MAIN procedure. The MAIN procedure now looks like:

MAIN

BEGIN

AT TOP OF PAGE

DO headings;

SELECT FROM subscribers

SORTED BY subscriber;

FOR EACH subscribers

DO details;

END

The entire program looks like:

/* list subscribers */

FILE subscribers IS "sub"

FIELDS IN subscribers ARE

subscriber, name, address,

city, state, zip;

MAIN

BEGIN

AT TOP OF PAGE

DO headings;

SELECT FROM subscribers

SORTED BY subscriber;

FOR EACH subscribers

DO details;

END

PROCEDURE headings /* print page headings */

BEGIN

PRINT TAB (15), "S u b s c r i b e r L i s t"

TAB (60), $today_date, NL;

PRINT "Subscriber", TAB(20), "Name and address",

NL, NL;

END

PROCEDURE details /* print one subscriber */

BEGIN

PRINT subscriber, TAB (20), name, NL,

TAB (20), address [1], NL;

IF address [2] NE "" THEN

PRINT TAB (20), address [2], NL;

PRINT TAB (20), city, ", ", state, " ",

zip, NL, NL;

END

PROBLEM 1:

Write a program which lists all of the magazines kept on file. Sort the list by the magazine code. Page headings should include a report title, the current date, the page number, and detail titles. The format of the detail is left up to you but the report must show the magazine code, the magazine title, and the number of subscriptions for that magazine. There is a section at the end of this chapter which contains sample solutions.

(Hint: The current page number can be shown by PRINTing the parameter $page.)