Generate a report from your database using CL.
Last month, in "Using the SQL Select Statement with RUNSQL," we reviewed how to query, or select, data from a SAMPLE database (also created in the referenced article). In last month's program, we simply displayed, using the CL command Send Program Message (SNDPGMMSG), the results of an SQL Select as shown below.
Class FIRST is effective on 2012-04-13
Class ABC is effective on 2012-04-15
End of file
This month, we will create a report, sorting the results of the SQL Select statement by class and effective date, as shown below.
5/20/2012 My Sample Report 7:40:27 Page: 1
Class Status Eff. Date
ABC 2 2012-04-15
FIRST 1 2012-04-13
** End of Report **
One item I do need to point out up front. To create this report, we will be using the free (as in no-charge) run-time support of the Control Language for Files (CLF) product 1BVSCLF. To create reports from CL-based applications as demonstrated by the following program, you only need to install the base product option of 1BVSCLF. There is no need to install CLF fee-based options such as the CLF precompiler.
Also note that if you happen to already have the base option of CLF installed on your system (perhaps due to a series of "CL Corner" articles I wrote back in 2009), there is no need to re-install CLF. There have been no PTFs, related to printer file support anyway, during the past three years.
Before we look at the CL program, let's review the printer file we will be using. Here's the DDS source for the printer file, named MYREPORT:
A R HEADING
A 2 2DATE(*SYS *YY) EDTCDE(Y)
A 2 28'My Sample Report'
A 2 59TIME
A +2'Page:'
A +1PAGNBR EDTCDE(3)
A 4 5'Class'
A 4 12'Status'
A 4 20'Eff. Date'
A R DETAIL SPACEB(1)
A CLASS 5 5
A STATUS 1 0 15
A EFFDATE L 20DATFMT(*USA)
A R END_OF_RPT SPACEB(2)
A 26'** End of Report **'
There are three record formats defined within the MYREPORT printer file:
- HEADING provides a heading for the report. The heading includes the current system date, the name of the report, the time the report was created, a page number, and column headings for the data to be listed.
- DETAIL provides the values of the &Class, &Status, and &EffDate (effective date) for the SAMPLE record being printed.
- END_OF_RPT provides an explicit indication of the last page of the report.
Assuming that the previously shown source is in member MYREPORT of source file QDDSSRC, you can create the printer file using the command CRTPRTF FILE(MYREPORT) SRCFILE(QDDSSRC).
Here is the program to create the previously shown report using the SAMPLE database records we wrote/inserted last month. Changes from last month's program, QRYSAMPLE, related to the second SQL Select that was demonstrated, are shown below in bold.
Pgm
DclF File(Sample) OpnID(MyResults)
Dcl Var(&EOF) Type(*Lgl)
Dcl Var(&RptLine) Type(*Char) Len(16)
Dcl Var(&Class) Type(*Char) Len(5) +
Stg(*Defined) DefVar(&RptLine 1)
Dcl Var(&Status) Type(*Char) Len(1) +
Stg(*Defined) DefVar(&RptLine 6)
Dcl Var(&EffDate) Type(*Char) Len(10) +
Stg(*Defined) DefVar(&RptLine 7)
Dcl Var(&NoDtaToWrt) Type(*Char) Len(1)
Dcl Var(&OvrFlwLin) Type(*Dec) Len(3 0)
Dcl Var(&CurPrtLin) Type(*Dec) Len(3 0)
CrtDupObj Obj(Sample) FromLib(*Libl) +
ObjType(*File) +
ToLib(QTemp) NewObj(MyResults)
RunSQL SQL('Insert into QTemp/MyResults +
(Select * +
from Sample +
where EffDate < ''2012-05-01'' +
order by Class, EffDate)') +
Commit(*None)
CallSubr Subr(ReadFile)
Return
Subr Subr(ReadFile)
OvrDBF File(Sample) ToFile(QTemp/MyResults)
ChgVar Var(&EOF) Value('0')
OpnFCLF FileID(MyReport) Usage(*Output) +
LvlChk(*No)
WrtRcdCLF FileID(MyReport) RcdFmt(Heading) +
RcdBuf(&NoDtaToWrt)
DoUntil Cond(&EOF = '1')
RcvF OpnID(MyResults)
MonMsg MsgID(CPF0864) Exec( +
ChgVar Var(&EOF) Value('1'))
If Cond(&EOF *EQ '0') Then(Do)
ChgVar Var(&Class) +
Value(&MyResults_Class)
ChgVar Var(&Status) +
Value(&MyResults_Status)
ChgVar Var(&EffDate) +
Value(&MyResults_EffDate)
WrtRcdCLF FileID(MyReport) +
RcdFmt(Detail) +
RcdBuf(&RptLine)
RtvFInfCLF FileID(MyReport) +
CurPrtLine(&CurPrtLin) +
PrtFOvrFlw(&OvrFlwLin)
If Cond(&CurPrtLin *GE &OvrFlwLin) +
Then(WrtRcdCLF FileID(MyReport) +
RcdFmt(Heading) +
RcdBuf(&NoDtaToWrt))
EndDo
Else Cmd( +
WrtRcdCLF FileID(MyReport) +
RcdFmt(End_Of_Rpt) +
RcdBuf(&NoDtaToWrt))
EndDo
CloFCLF FileID(MyReport)
Close OpnID(MyResults)
DltOvr File(Sample)
EndSubr
EndPgm
Assuming that the preceding CL source is stored in member RPTSAMPLE of source file QCLSRC, you can create the program using the command CRTBNDCL PGM(RPTSAMPLE). To run the program, use the command CALL PGM(RPTSAMPLE).
Now let's look at what the RPTSAMPLE program is doing.
The first change to the program is to declare the variables used by the various record formats of the MYREPORT printer file. The IBM-provided Declare File (DCLF) command does not support printer files, so you need to handle the printer file as if it were program-described. To program-describe the various record formats of the MYREPORT printer file, you can use the Display File Field Description (DSPFFD) command. This command will provide you with the order, type, and size of variables referenced by each format.
As only record format DETAIL contains references to program variables (Class, Status, and EffDate), this is the only record format you need to explicitly define. Using the output of the DSPFFD command, you define one variable (&RptLine in the provided sample program), which is declared with the length (16 bytes) of the DETAIL record format. Then, using *Defined storage, you can declare the fields found in the record format (Class, Status, and EffDate). As the CL DCL command does not support the definition of zoned decimal fields (which Status is) and date fields (which EffDate is), the program declares these variables as character fields of the proper length (1 byte and 10 bytes, respectively). Later in this article, I will point out that an alternative to the use of *Defined storage exists. I believe, though, that the use of *Defined storage provides the best documentation of subsequent processing the program will be doing (in the ReadFile subroutine).
Having defined the layout of the DETAIL record format, the program then declares three additional variables:
- &NoDtaToWrt is declared as a one-byte character variable. This variable is later used as a command parameter to represent the record format values for formats HEADING and END_OF_RPT. These record formats do not actually contain any variables and are of zero length (as determined with the DSPFFD command previously referenced). This variable is used to simply indicate that there is no data to write associated with the formats HEADING and END_OF_RPT.
- &OvrFlwLin is declared as a three-digit packed decimal variable and, strictly speaking, is not necessary in the RPTSAMPLE program. As there are only two records in the SAMPLE database that meet the criteria specified by the SQL Select statement, you do not have to worry about page overflows. In real life, though, a report can span more than one page; RPTSAMPLE demonstrates how to handle this. The variable &OvrFlwLin is used to hold the overflow line of the MYREPORT printer file.
- &CurPrtLin is declared as a three-digit packed decimal value and, as with the &OvrFlwLin variable, is not strictly necessary. This variable is used to hold the current print line of the MYREPORT printer file.
With the printer file-related declares out of the way, the next change from last month is in the ReadFile subroutine. Prior to falling into the DoUntil processing of MYRESULTS records, RPTSAMPLE opens printer file MYREPORT using the Open File using CLF (OPNFCLF) command. When opening the file, the program specifies that the file will be used for output and that level-checking is not to be used. LVLCHK(*NO) is necessary when using program-described files.
Having opened the printer file, the RPTSAMPLE program then writes the MYREPORT HEADING record format using the Write Record using CLF (WRTRCDCLF) command. It is here that the "dummy" variable &NoDtaToWrt is used with the RcdBuf (Record buffer) parameter of the WRTRCDCLF command.
Within the DoUntil loop, after successfully reading a record from MYRESULTS with the RCVF command (that is, variable &EOF is off: '0'), the program uses three CHGVAR commands to set the &RptLine *Defined variables of &Class, &Status, and &EffDate to the values read from MYRESULTS (variables &MyResults_Class, &MyResults_Status, and &MyResults_EffDate, respectively). The program then writes the MYREPORT DETAIL record format using the WRTRCDCLF command. Here, the actual record buffer of &RptLine is specified with the RcdBuf parameter.
It is these CHGVAR commands that prompted the use of *Defined storage when declaring the &Class, &Status, and &EffDate variables of &RptLine. An alternative approach would be to use CL's substring capability to set the proper values of &RptLine, as shown with the following three CHGVAR commands.
ChgVar Var(%sst(&RptLine 1 5)) +
Value(&MyResults_Class)
ChgVar Var(%sst(&RptLine 6 1)) +
Value(&MyResults_Status)
ChgVar Var(%sst(&RptLine 7 10)) +
Value(&MyResults_EffDate)
To my way of thinking, commands such as ChgVar Var(&Class) Value(&MyResults_Class) provide better understanding of the program flow than ChgVar Var(%sst(&RptLine 1 5)) Value(&MyResults_Class), but the choice is up to you.
After writing each DETAIL record of the report, RPTSAMPLE uses the Retrieve File Information using CLF (RTVFINFCLF) command to determine the current print line of the MYREPORT printer file. After the RTVFINFCLF command completes, the variable &CurPrtLin will be set to the current print line. If &CurPrtLin is greater than or equal to the page overflow value (&OvrFlwLin), then the program will advance to the next page of the report using the WRTRCDCLF command.
When all MYRESULTS records have been processed (that is, variable &EOF is on: '1'), RPTSAMPLE prints the line "** End of Report **" using the WRTRCDCLF command (record format END_OF_RPT) and exits the DoUntil loop.
Upon exiting the DoUntil loop, the program closes the printer file using the Close File using CLF (CLOFCLF) command.
That's it. We have now created a custom report that lists selected records of the SAMPLE database, using only a CL program. If, in the past, you resorted to using a query product (with a "close enough" report layout) or writing a program using a language such as RPG, COBOL, or C, you now have another option; just use a CL program.
For those of you who do have the precompiler option of CLF installed, below is the equivalent CLF program to create the report discussed earlier in this article. Changes from last month's program are shown in bold. Note that all references to OpenID(MyResults) have been removed (which is difficult for me to show in bold). To compile the program, use the command CRTBNDCLF PGM(RPTSAMPLE).
Pgm
DclFCLF FileID(MyReport)
DclF File(Sample)
Dcl Var(&EOF) Type(*Lgl)
Dcl Var(&OvrFlwLin) Type(*Dec) Len(3 0)
Dcl Var(&CurPrtLin) Type(*Dec) Len(3 0)
CrtDupObj Obj(Sample) FromLib(*Libl) +
ObjType(*File) +
ToLib(QTemp) NewObj(MyResults)
RunSQL SQL('Insert into QTemp/MyResults +
(Select * +
from Sample +
where EffDate < ''2012-05-01'' +
order by Class, EffDate)') +
Commit(*None)
CallSubr Subr(ReadFile)
Return
Subr Subr(ReadFile)
OvrDBF File(Sample) ToFile(QTemp/MyResults)
ChgVar Var(&EOF) Value('0')
OpnFCLF FileID(MyReport) Usage(*Output)
WrtRcdCLF FileID(MyReport) RcdFmt(Heading)
DoUntil Cond(&EOF = '1')
RcvF
MonMsg MsgID(CPF0864) Exec( +
ChgVar Var(&EOF) Value('1'))
If Cond(&EOF *EQ '0') Then(Do)
WrtRcdCLF FileID(MyReport) +
RcdFmt(Detail)
RtvFInfCLF FileID(MyReport) +
CurPrtLine(&CurPrtLin) +
PrtFOvrFlw(&OvrFlwLin)
If Cond(&CurPrtLin *GE &OvrFlwLin) +
Then(WrtRcdCLF FileID(MyReport) +
RcdFmt(Heading))
EndDo
Else Cmd( +
WrtRcdCLF FileID(MyReport) +
RcdFmt(End_Of_Rpt))
EndDo
CloFCLF FileID(MyReport)
Close
DltOvr File(Sample)
EndSubr
EndPgm
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online