Michael Coughlan
Page 22
The ShopReceiptsFile uses the character H to indicate the ShopDetails record (the header record) and S to indicate the SaleReceipt record (the sales record). To detect the type of record read into the buffer, you could use statements such as IF TypeCode = "H" or IF TypeCode = "S". But this is COBOL. It offers a better way. You can define condition names to monitor the type code so that if it contains H the condition name ShopHeader is set to true, and if it contains S the condition name ShopSale is set to true. The record descriptions required to accommodate these changes for the ShopReceiptsFile are shown in Example 8-2.
159
Chapter 8 ■ advanCed Sequential FileS
Example 8-2. ShopReceiptsFile Record Descriptions with Type Code
FILE SECTION.
FD ShopReceiptsFile.
01 ShopDetails.
02 TypeCode PIC X.
88 ShopHeader VALUE "H".
88 ShopSale VALUE "S".
02 ShopId PIC X(5).
02 ShopLocation PIC X(30).
01 SaleReceipt.
02 TypeCode PIC X.
02 ItemId PIC X(8).
02 QtySold PIC 9(3).
02 ItemCost PIC 999V99.
A graphical representation of the new record descriptions is shown in Figure 8-4. In this case, there is a ShopDetails record in the buffer. Again, both record descriptions are current (live), but only the ShopDetails record description makes sense for the values in the buffer.
Figure 8-4. Representation of a record buffer that includes the TypeCode
When you examined the file description given in Example 8-2, perhaps it occurred to you to ask, why have
condition names been defined only for the ShopDetails record and not for the SaleReceipt record? The answer is that TypeCode in both records maps on to the same area of storage; and that because both record descriptions, including the condition names, are current, it does not matter which record is read into the buffer—the condition names can detect it.
Example Program
The program specification given at the beginning of the chapter required you to write a program to process the ShopReceiptsFile. For each shop in the file, you were asked to produce a summary line that shows the ShopId and the total value of sales for that shop. The program to implement the specification is given in Listing 8-1.
160
Chapter 8 ■ advanCed Sequential FileS
Listing 8-1. Summarizes the Header and Sale records of the ShopReceiptsFile
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing8-1.
AUTHOR. Michael Coughlan.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
Select ShopReceiptsFile ASSIGN TO "Listing8-1-ShopSales.Dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD ShopReceiptsFile.
01 ShopDetails.
88 EndOfShopReceiptsFile VALUE HIGH-VALUES.
02 RecTypeCode PIC X.
88 ShopHeader VALUE "H".
88 ShopSale VALUE "S".
02 ShopId PIC X(5).
02 ShopLocation PIC X(30).
01 SaleReceipt.
02 RecTypeCode PIC X.
02 ItemId PIC X(8).
02 QtySold PIC 9(3).
02 ItemCost PIC 999V99.
WORKING-STORAGE SECTION.
01 PrnShopSalesTotal.
02 FILLER PIC X(21) VALUE "Total sales for shop ".
02 PrnShopId PIC X(5).
02 PrnShopTotal PIC $$$$,$$9.99.
01 ShopTotal PIC 9(5)V99.
PROCEDURE DIVISION.
ShopSalesSummary.
OPEN INPUT ShopReceiptsFile
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ
PERFORM SummarizeCountrySales
UNTIL EndOfShopReceiptsFile
CLOSE ShopReceiptsFile
STOP RUN.
SummarizeCountrySales.
MOVE ShopId TO PrnShopId
MOVE ZEROS TO ShopTotal
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ
161
Chapter 8 ■ advanCed Sequential FileS
PERFORM SummarizeShopSales
UNTIL ShopHeader OR EndOFShopReceiptsFile
MOVE ShopTotal TO PrnShopTotal
DISPLAY PrnShopSalesTotal.
SummarizeShopSales.
COMPUTE ShopTotal = ShopTotal + (QtySold * ItemCost)
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ.
Some basic test data and the results produced by running the program against this test data are shown in Figure 8-5.
Figure 8-5. Basic test data for Listing 8-1
When you consider the solution produced in Listing 8-1, you may be a little puzzled. Where is the IF statement that checks whether the record is a ShopHeader or a ShopSale record? The answer to this question lies in the approach to the problem solution. Many programmers would solve the problem by having a loop to read the records in the file and an IF statement to check what kind type of record has been read. If a ShopSale record was read, then the required computations would be done; and if a ShopDetails record was read, the summary line would be produced and displayed. This is not a terrible solution for a problem of this size; but when you get to control breaks—a type of problem of which this is a near relation—this type of solution quickly becomes complicated.
The solution adopted in Listing 8-1 involves examining the structure of the records in the ShopReceiptsFile and producing a solution that reflects that structure. What do I mean by the structure of the file? The records in the file are not thrown randomly into the file: they are grouped by shop, and each grouping starts with a ShopDetails header record followed by many SaleReceipt records. The solution in Listing 8-1 reflects the structure of the file. It has a loop 162
Chapter 8 ■ advanCed Sequential FileS
to process the SaleReceipt records and an outer loop to process the whole file. You know you have come to the end of the sales records for a particular shop when you encounter the ShopDetails record for the next shop. At that point, you display the summary information you have accumulated for the previous shop. A graphical representation of this solution as applied to the test data is given in Figure 8-6.
Figure 8-6. Representation of the solution as applied to the test data
■ Note this solution uses the Micro Focus LINE SEQUENTIAL extension. the reason is that when a file contains records of different lengths, the system has to use a record terminator to detect when one record ends and the next begins. the record terminator is specified by the language implementer. Where the terminator is not a fixed implementer default, it can be specified by using the RECORD DELIMITER IS clause in the file’s SELECT and ASSIGN clause.
Because there is no generic, standard way of specifying the terminator, i chose to use the Micro Focus LINE SEQUENTIAL
extension. When LINE SEQUENTIAL is used, each record is terminated by the carriage return and line feed aSCii characters. adopting this extension has the added benefit that the test data can be written using a standard text editor such as Microsoft notepad.
163
Chapter 8 ■ advanCed Sequential FileS
Specification Amendment
In a file such as ShopReceiptsFile, which consists of groups that contain a header record followed by many body records, there is often a third type of record. A footer record is frequently used to ensure that the group is complete and that none of the records in the group body has been lost. The footer record might simply contain a count of the records in the group body, or it might do some calculations to produce a checksum.
Let’s amend the ShopReceiptsFile to include the footer record; and let’s amend the specification to say that if the record count in the footer record is not the same as t
he actual record count, then an error message should be displayed instead of the sales total. The footer record is indicated by the F character.
A program to implement the specification is given in Listing 8-2.
Listing 8-2. Summarizes the Header, Sale, and Footer records of the ShopReceiptsFile
IDENTIFICATION DIVISION.
PROGRAM-ID. Listing8-2.
AUTHOR. Michael Coughlan.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
Select ShopReceiptsFile ASSIGN TO "Listing8-2-ShopSales.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD ShopReceiptsFile.
01 ShopDetails.
88 EndOfShopReceiptsFile VALUE HIGH-VALUES.
02 TypeCode PIC X.
88 ShopHeader VALUE "H".
88 ShopSale VALUE "S".
88 ShopFooter VALUE "F".
02 ShopId PIC X(5).
02 ShopLocation PIC X(30).
01 SaleReceipt.
02 TypeCode PIC X.
02 ItemId PIC X(8).
02 QtySold PIC 9(3).
02 ItemCost PIC 999V99.
01 ShopSalesCount.
02 TypeCode PIC X.
02 RecCount PIC 9(5).
WORKING-STORAGE SECTION.
01 PrnShopSalesTotal.
02 FILLER PIC X(21) VALUE "Total sales for shop ".
02 PrnShopId PIC X(5).
02 PrnShopTotal PIC $$$$,$$9.99.
164
Chapter 8 ■ advanCed Sequential FileS
01 PrnErrorMessage.
02 FILLER PIC X(15) VALUE "Error on Shop: ".
02 PrnErrorShopId PIC X(5).
02 FILLER PIC X(10) VALUE " RCount = ".
02 PrnRecCount PIC 9(5).
02 FILLER PIC X(10) VALUE " ACount = ".
02 PrnActualCount PIC 9(5).
01 ShopTotal PIC 9(5)V99.
01 ActualCount PIC 9(5).
PROCEDURE DIVISION.
ShopSalesSummary.
OPEN INPUT ShopReceiptsFile
PERFORM GetHeaderRec
PERFORM SummarizeCountrySales
UNTIL EndOfShopReceiptsFile
CLOSE ShopReceiptsFile
STOP RUN.
SummarizeCountrySales.
MOVE ShopId TO PrnShopId, PrnErrorShopId
MOVE ZEROS TO ShopTotal
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ
PERFORM SummarizeShopSales
VARYING ActualCount FROM 0 BY 1 UNTIL ShopFooter
IF RecCount = ActualCount
MOVE ShopTotal TO PrnShopTotal
DISPLAY PrnShopSalesTotal
ELSE
MOVE RecCount TO PrnRecCount
MOVE ActualCount TO PrnActualCount
DISPLAY PrnErrorMessage
END-IF
PERFORM GetHeaderRec.
SummarizeShopSales.
COMPUTE ShopTotal = ShopTotal + (QtySold * ItemCost)
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ.
GetHeaderRec.
READ ShopReceiptsFile
AT END SET EndOfShopReceiptsFile TO TRUE
END-READ.
165
Chapter 8 ■ advanCed Sequential FileS
The new test data and the result of running the program against that test data are shown in Figure 8-7.
Figure 8-7. Test data and results for Listing 8-2
Some Comments about the Program
The GetHeaderRec paragraph has only one statement. Ordinarily this would be bad practice, but in this instance, I wanted to use the paragraph name to indicate the purpose of this particular READ statement. In a real program, the PERFORM GetHeaderRec statements would be replaced with the READ in the GetHeaderRec paragraph.
The logic of the program has been changed, because now the end of the shop group is indicated by the presence of a footer record. The sale records for each shop group are counted by means of the PERFORM..VARYING. For a variety of reasons, including book space constraints, the only error the program checks for is missing sale receipt records.
It is assumed that in all other respects, the file is correct.
Printer Sequential Files
In a business or enterprise environment, the ability to print reports is an important property for a programming language. COBOL allows programmers to write to the printer, either directly or through an intermediate print file.
COBOL treats the printer as a serial file but uses a special variant of the WRITE verb to control the placement of lines on the page. Printing is regarded as so important that not only does COBOL have the printer sequential files discussed in this section, but it also supports a special set of declarations and verbs that together constitute the COBOL Report Writer.
The Report Writer introduces elements of declarative programming to COBOL. It is discussed in detail in a later chapter.
166
Chapter 8 ■ advanCed Sequential FileS
SELECT and ASSIGN
As with ordinary sequential files, the internal name used for the print file is associated with an external device, which could be an actual printer or a print file. A print file is a file that contains embedded printer control codes such as form feed. Generally, you write to a print file; but in a COBOL programming shop, your program may well have direct control of the printer. The metalanguage for print files is given in Figure 8-8. Since ORGANIZATION IS SEQUENTIAL
is the default it may be omitted.
Figure 8-8. Print file SELECT and ASSIGN metalanguage
Notes
Where direct control of the printer is assumed, the internal print name is assigned to an ImplementerName, which depends on the vendor. For instance, in HP COBOL (really VAX COBOL), the ImplementerName is LINE-PRINTER
(see Example 8-3) and the name is attached to an actual printer by a LINE-PRINTER IS DeviceName entry in the SPECIAL-NAMES paragraph (CONFIGURATION SECTION, ENVIRONMENT DIVISION).
Example 8-3. SELECT and ASSIGN clauses for a Print File and a Print Device
SELECT MembershipReport ASSIGN TO "MembershipRpt.rpt".
SELECT MembershipReport ASSIGN TO LINE-PRINTER.
What Is in a Report
Even when the Report Writer is not directly used, a report created with a printer sequential file consists of groups of printed lines of different types. For instance, suppose you want to print a report that lists the membership of your local golf club. This report might consist of the following types of print lines:
• Page Heading
Rolling Greens Golf Club - Membership Report
• Page Footing
Page: PageNum
• Column Headings
MemberID Member Name Type Gender
• Membership detail line
MemberID MemberName MembershipType Gender
• Report Footing
**** End of Membership Report ****
To set up the printer sequential file, you must create an FD for the file and a print record for each type of print line that will appear on the report. For instance, for the golf club membership report, you have to have the records shown in Example 8-4.
167
Chapter 8 ■ advanCed Sequential FileS
Example 8-4. Print Lines Required for the Golf Club Membership Report
01 PageHeading.
02 FILLER PIC X(44)
VALUE "Rolling Greens Golf Club - Membership Report".
01 PageFooting.
02 FILLER PIC X(15) VALUE SPACES.
02 FILLER PIC X(7) VALUE "Page : ".
02 PrnPageNum PIC Z9.
01 ColumnHeadings PIC X(41)
VALUE "MemberID Member Name Type Gender".
01 MemberDetailLine.
02 FILLER PIC X VALUE SPACES.
02 PrnMemberId PIC 9(5).
02 FILLER PIC X(4) VALUE SPACES.
02 PrnMemberName PIC X(20).
02 FILLER PIC XX VALUE SPACES.
02 PrnMemberType PIC X.
02 FILLER PIC X(4) VALUE SPACES.
02 PrnGender PIC X.
01 ReportFooting PIC X(38)
VALUE "**** End of Membership Report ****".
Problem of Multiple Print Records
When you reviewed the different types of print lines in Example 8-4, you may have realized that there is a problem.
As you saw in the previous section, if a file is declared as having multiple record types, all the records map on to the same physical area of storage. This does not cause difficulties if the file is an input file, because only one type of record at a time can be in the buffer. But as you can see from the print line declarations in Example 8-4, the information in many print lines is static. It is assigned using the VALUE clause and instantiated as soon as the program starts. This means all the record values have to be in the record buffer at the same time, which is obviously impossible. In fact, to prevent the creation of print records in the FILE SECTION, there is a COBOL rule stating that, in the FILE SECTION, the VALUE clause can only be used with condition names (that is, it cannot be used to give an item an initial value).
Solution to the Multiple Print Record Problem
The solution to the problem of declaring print records is to declare the print line records in the WORKING-STORAGE
SECTION and to declare a record in the file’s FD entry in the FILE SECTION, which is the size of the largest print line record. You print a print line by moving it from the WORKING-STORAGE SECTION, to the record in the FILE SECTION; then that record is written to the print file. This is shown graphically in Example 8-5.
168
Chapter 8 ■ advanCed Sequential FileS
Example 8-5. Writing to a Print File
WRITE Syntax Revisited
When I discussed the WRITE statement in the previous chapter, I noted that I was postponing discussion of the ADVANCING