In order to get any of the computed values to print, COMPUTE must be used in conjunction with the BREAK command.. In order to print the computed values, you must issue a BREAK command to
Trang 1FROM EMPLOYEE E,
PROJECT P,
PROJECT_HOURS PH
WHERE E.EMPLOYEE_ID = PH.EMPLOYEE_ID
AND P.PROJECT_ID = PH.PROJECT_ID
ORDER BY E.EMPLOYEE_ID, P.PROJECT_ID, PH.TIME_LOG_DATE;
Printing data in a page footer
You can print data as part of the page footer using the same method just shown for the page header The only difference
is that you would normally use the OLD_VALUE clause with the COLUMN command rather than NEW_VALUE That's because when SQL*Plus prints the footer, it has already read the next detail record from the database Using NEW_VALUE for data in the footer would cause the footer to display information pertaining to the next page in the reportnot something you would normally want to happen
Totals and Subtotals
SQL*Plus allows you to print totals and subtotals in a report To do this, you use a combination of the BREAK
command and one or more COMPUTE commands This section continues where the previous section left off It will show you how to modify the master/detail report created earlier so that it totals the hours and dollars by project and by employee You will also see how to print grand totals for these columns at the end of the report
The COMPUTE Command
The COMPUTE command defines summary calculations needed in a report You can use COMPUTE in conjunction with BREAK to calculate and print column totals, averages, minimum and maximum values, and so on These
calculations are performed by SQL*Plus as the report runs
Syntax of the COMPUTE command
The syntax for the COMPUTE command looks like this:
COMP[UTE] [{AVG¦COU[NT]¦MAX[IUM] ¦ MIN[IMUM] ¦
NUM[BER] ¦ STD ¦ SUM¦VAR [IANCE]} [LABEL label_text]
OF column_name
ON {group_column_name¦ROW¦REPORT} ]
where:
COMP[UTE]
May be abbreviated to COMP Entering COMPUTE with no parameters causes SQL*Plus to list all currently defined computations
Trang 2
AVG
Computes the average of all non-null values for a column AVG only applies to columns of type NUMBER
COU[NT]
Computes the total number of non-null values for a column COUNT may be used with columns of any datatype, and may be abbreviated to COU
MAXI[MUM]
Computes the maximum value returned for a column MAXIMUM may be abbreviated as MAX, and applies to
columns of type NUMBER, CHAR, VARCHAR2, NCHAR, and NVARCHAR2
MINI[UM]
Computes the minimum value returned for a column MINIMUM may be abbreviated as MIN, and applies to columns
of type NUMBER, CHAR, VARCHAR2, NCHAR, and NVARCHAR2
NUM[BER]
Similar to COUNT, but computes the number of all values, including nulls This applies to columns of any datatype, and may be abbreviated to NUM
STD
Computes the standard deviation of all non-null values for a column STD applies only to columns of type NUMBER
SUM
Computes the sum of all non-null values for a column SUM applies only to columns of type NUMBER
VAR[ANCE]
Computes the variance of all non-null values for a column VARIANCE applies only to columns of type NUMBER, and may be abbreviated to VAR
LABEL label_text
Allows you to specify a label for the computed value If possible, this label will be printed to the left of the computed value The label text may be enclosed in quotes, either single or double To embed a quote within the label, when that label has been quoted, place two quote characters back to back
column_name
Is the name of the column you are summarizing If it is a computed column, then the expression is the name If your SELECT statement aliases the column, then you must use that alias here
Trang 3
group_column_name
Causes SQL*Plus to restart the calculation every time this column changes Typically, the report is sorted or grouped by this column, and then the computed value is printed once for each distinct value of the group column
ROW
Causes the computation to be performed once for each row returned by the query
REPORT
Causes the computation to be performed at the end of the report, and to include values from all rows REPORT is used for grand totals
The COMPUTE command is complex, and can be intimidating the first time you go to use it In order to get any of the computed values to print, COMPUTE must be used in conjunction with the BREAK command The next section will show you how this is done
Printing computed values
The COMPUTE command tells SQL*Plus what columns to summarize and over what range of records That's all it does By itself, it won't cause anything to print In order to print the computed values, you must issue a BREAK
command to define breaks on the same report elements used in your COMPUTE command For example, if you total hours by project ID, then you need a project ID break in order to print that total
COMPUTE commands are cumulative
With the BREAK command, you have only one break setting, and each new BREAK command overwrites that setting with a new one That's not the case with COMPUTE Each defined computation is a separate entity, and COMPUTE commands do not overwrite each other COMPUTE definitions also stick for the duration of a SQL*Plus session A COMPUTE setting issued for one report may stick around to affect the printing of subsequent reports SQL*Plus
provides the CLEAR COMPUTES command to help you deal with this CLEAR COMPUTES causes SQL*Plus to erase all computations so you can start over If you are using COMPUTE in a report script, you may want to include a CLEAR COMPUTES command to avoid any possible contamination from previous reports that may have used
COMPUTE
Printing Subtotals
The Project Hours and Dollars Detail report has two numeric columns showing the hours logged to a project together with the resulting dollar amount that was
Trang 4
charged You can easily see that it would be desirable to total these for each project and employee The following five commands will accomplish this:
CLEAR COMPUTES
COMPUTE SUM LABEL Totoals OF hours_logged ON project_id
COMPUTE SUM LABEL Totals OF dollars_charged ON project_id
COMPUTE SUM LABEL Totals OF hours_logged ON employee_id
COMPUTE SUM LABEL Totals OF dollars_charged ON employee_id
The first command simply clears any existing computations that may be defined The next two commands summarize the hours and dollars by project The last two commands do the same thing except that the totals are for the employee, and cover all projects to which an employee has charged hours Here's how the output will look when you run the modified script:
The Fictional Company
I.S Department Project Hours and Dollars Detail
=========================================================================
Employee: 101 Jonathan Gennick
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
1001 Corporate Web site 01-Jan-1998 1 $169.00
01-Mar-1998 3 $507.00
01-May-1998 5 $845.00
01-Jul-1998 7 $1,183.00
01-Sep-1998 1 $169.00
01-Nov-1998 3 $507.00
******* ************************** - -
Totals 20 $3,380.00
1002 Year 2000 Fixes 01-Feb-1998 7 $1,183.00
01-Apr-1998 1 $169.00
01-Jun-1998 3 $507.00
1005 TCP/IP Implementation 01-Jan-1998 5 $845.00
01-Mar-1998 7 $1,183.00
01-May-1998 1 $169.00
01-Jul-1998 3 $507.00
01-Sep-1998 5 $845.00
01-Nov-1998 7 $1,183.00
******* ************************** - -
Totals 28 $4,732.00
- -
116 $19,604.00
Trang 5
Notice that the label Totals appears in the project_id column SQL*Plus always places the label you specify into the ON column of the COMPUTE command The label will be formatted according to the rules specified in that column's
COLUMN command
Only one label can be printed for project totals, though there are two COMPUTE commands Had you specified two different labels, the first one would have taken precedence
Why no label for the employee totals? Because this is a master/detail report, and the NOPRINT option has been used to suppress printing of the employee_name and employee_id columns Normally, SQL*Plus would print the COMPUTE label in the employee_id column, but that can't be done if the column is not being printed There is really nothing you can do if you aren't happy with this behavior You either have to live with it or avoid master/detail reports
The width of the label identifying the project totals is limited by the width of the project_idcolumn, which simply won't hold a longer, more descriptive label such as Project Totals However, there are a couple of things you can do to make room for a longer label The first and most obvious thing is simply to make the project_column wider Change the COLUMN command to widen the field from 7 to 14 digits:
COLUMN project_id HEADING Proj ID FORMAT 99999999999999
Just be sure to bump up the linesize setting by the same amount, and also adjust the page headers and footers
A less obvious approach would be to change the computations so the project totals are summarized for each project name rather than for each project ID The resulting commands would be:
COMPUTE SUM LABEL Project Totals OF hours_logged ON project_name
COMPUTE SUM LABEL Project Totals OF dollars_charged ON project_name
The output would then look like this:
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
1001 Corporate Web Site 01-Jan-1998 1 $169.00
01-Mar-1998 3 $507.00
01-May-1998 5 $845.00
01-Jul-1998 7 $1,183.00
01-Sep-1998 1 $169.00
01-Nov-1998 3 $507.00
************************** - -
Project Totals 20 $3,380.00
Trang 6
You now have room for a more descriptive label, and there is the added benefit that it looks better with the label
indented closer to the printed totals
Print Grand Totals
The REPORT keyword is used to compute and print totals for an entire report Use REPORT in the ON clause of a COMPUTE statement to define report-level computations Use REPORT with the BREAK command to enable a report break that will cause the report-level computed values to print
To print grand totals for the project_hours and dollars_charged columns, add the following two lines to the script file: COMPUTE SUM LABEL Grand Totals OF hours_logged ON REPORT
COMPUTE SUM LABEL Grand Totals OF dollars_charged ON REPORT
Notice that instead of specifying a column in the ON clause, the keyword REPORT has been used This tells SQL*Plus
to sum the data over the entire report Also notice that the LABEL clause has been used Normally the label would print
in the column specified in the ON clause In cases like this where there is no ON column, SQL*Plus will place the labels in the first column of the report
The next thing to do is to modify the BREAK command by adding a report break Forget to do this and the report totals will not print The final version of the BREAK command looks like this:
BREAK ON REPORT -
ON employee_id SKIP PAGE NODUPLICATES -
ON employee_name NODUPLICATES -
ON project_id SKIP 2 NODUPLICATES -
ON project_name NODUPLICATES
The REPORT break was added to the beginning of the BREAK command because it is the outermost break The
position doesn't really matter because SQL*Plus always makes the report break outermost, but I like to put it first
anyway for the sake of clarity
If you run the report now, the grand totals will be printed on a page by themselves at the end of the report Here's how that output will look:
The Fictional Company
I.S Department Project Hours and Dollars Details
============================================================
Employee: 113 Jacob Marley
Trang 7
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
- -
786 $110,779.00
Notice three things about how the report totals are printed First, notice that they printed on a page by themselves Next, notice that the page with the grand totals still shows an employee name in the page header Finally, notice that the
Grand Totals label did not print as expected in the first column I'll explain all of these oddities next
First the pagination issue Before SQL*Plus executes a report break, it first executes all the other breaks Execution begins with the innermost break and proceeds outwards until the report break actions are executed In this example, SQL*Plus will skip two linesthe project break and the skip to a new pagethe employee break, and then print the report totals This is usually the behavior you want when printing a master/detail report You may intend to give each
employee his own section of the report so he can double-check his hours Since the grand total doesn't really belong with any one employee, you don't want it on the pages you are giving out
The last employee's name printed on the page header simply because it was the last value retrieved from the database It would be nice if SQL*Plus were smart enough to make this value null or blank, but it isn't The value in the header is refreshed only when a new value is read from the database, and in the case of a report break, that simply doesn't happen This is only an issue on master/detail reports when you use variables to include report data in the header
The final item to notice, and the only one you can do anything about, is the lack of a label for the grand total values I did say that SQL*Plus puts the label for report-level calculations in the first column of the report Contrary to what you might intuitively expect, SQL*Plus bases the first column on the SELECT statement, not on what is actually printed When this report was converted to a master/ detail report, printing of the first two columns was suppressed using the NOPRINT clause of the COLUMN command No employee_id column, no Grand Totals label
Since the employee_id and employee_name columns are not being printed, their position in the SELECT statement is irrelevant You can move them to the end, making project_id the first column, widen the project_id column to hold 12 characters instead of 7, and the Grand Totals label will now print in the first column of the report
The final listing, complete with the changes that allow the Grand Totals label to print, is shown next To widen the project_id column to accommodate 12
Trang 8
characters, 5 extra leading spaces were inserted into the project_id column title The linesize was also adjusted from 66 to 71, and 5 equal-sign characters were added to the ruling lines in the header and footer
Setup pagesize paremeters
Set NEWPAGE 0
SET PAGESIZE 35
Set the linesize, which must match the number of equal signs used
for the ruling lines in the headers and footers
SET LINESIZE 71
Setup page headings and footings
TTITLE CENTER The Fictional Company SKIP 3 -
LEFT I.S Department -
RIGHT Project Hours and Dollars Detail SKIP 1 -
LEFT ============================================================= -
SKIP 2 Employee: lsquo; FORMAT 9999 emp_id_var emp_name_var SKIP 3
BTITLE LEFT ============================================================== -
SKIP 1 -
RIGHT PAGE FORMAT 999 SQL.PNO
Format the columns
COLUMN employee_id NEW_VALUE emp_id_Var NOPRINT
COLUMN employee_name NEW_VALUE emp_name_var NOPRINT
COLUMN project_id HEADING Proj ID FORMAT 9999
COLUMN project_name HEADING Project Name FORMAT A26 WORD_WRAPPED
COLUMN time_log_date HEADING Date FORMAT A11
COLUMN hours_logged HEADING Hours FORMAT 9,999
COLUMN dollars_charged HEADING Dollars¦Charged FORMAT $999,999.99
Breaks and Computations
BREAK ON REPORT -
ON employee_id SKIP PAGE NODUPLICATES -
ON employee_name NODUPLICATES -
ON project_id SKIP 2 NODUPLICATES -
ON project_name NODUPLICATES -
CLEAR COMPUTES
COMPUTE SUM LABEL Project Totals OF hours_logged ON project_name
COMPUTE SUM LABEL Project Totals OF dollars_charged ON project_name
COMPUTE SUM LABEL Totals OF hours_logged ON employee_id
COMPUTE SUM LABEL Grand Totals OF dollars_charged ON REPORT
COMPUTE SUM LABEL Grand Totals OF hours_charged ON REPORT
COMPUTE SUM LABEL Grand Totals OF dollars_charged ON REPORT
Execute the query to generate the report
SELECT P.PROJECT_ID,
P.PROJECT_NAME,
TO_CHAR(PH.TIME_LOG_DATE, dd-Mon-yyyy) time_log_date,
PH.HOURS_LOGGED,
PH.DOLLARS_CHARGED,
E.EMPLOYEE_ID,
E.EMPLOYEE_NAME
Trang 9
FROM EMPLOYEE E;
PROJECT P,
PROJECT_HOURS PH
WHERE E.EMPLOYEE_ID = PH.EMPLOYEE_ID
AND P.PROJECT_ID = PH.PROJECT_ID
ORDER BY E.EMPLOYEE_ID, P.PROJECT_ID, PH.TIME_LOG_DATE;
Here is the output produced by the final version of this script Notice that the Grand Totals label does appear in the
project_id column at the end of the report
The Fictional Company
I.S Department Project Hours and Dollars Detail
========================================================================
Employee: 101 Jonathan Gennick
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
1001 Corporate Web Site 01-Jan-1998 1 $169.00
01-Mar-1998 3 $507.00
01-May-1998 5 $845.00
01-Jul-1998 7 $1,183.00
01-Sep-1998 1 $169.00
01-Nov-1998 3 $507.00
************************** - -
Project Totals 20 3,380.00
detail for several projects
1005 TCP/IP Implementation 01-Jan-1998 5 $845.00
01-Mar-1998 7 $1,183.00
01-May-1998 1 $169.00
01-Jul-1998 3 $507.00
01-Sep-1998 5 $845.00
01-Nov-1998 7 $1,183.00
************************** - -
Project Totals 28 $4,732.00
************ - -
116 $19,604.00
severalpages of output
The Fictional Company
I.S Department Project Hours and Dollars Detail
======================================================================
Trang 10
Employee: 113 Jacob Marley
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
- -
Grand Totals 786 $110,779.00
When printing totals and grand totals, be sure that the summarized columns are wide enough to accommodate the final totals None of the individual Dollars Charged values in this report required more than four digits to the left of the decimal, but the final total required six
Other Reporting Topics
In addition to what you've read so far in this chapter, there are several other techniques and topics worth discussing One
is a method for getting the current date into your page titles Considering how easy it is to do page numbers, it's
surprisingly difficult to do dates Report headers and footers function similarly to page headers and footers, except that they print just once, at the beginning and end of a report
If you find yourself printing reports only because you need to look at computed values such as group totals and report totals, you will want to read the section on summary reports that follows You can save yourself a lot of network and other overhead by summarizing your data on the server instead of returning all the detail records and making SQL*Plus
do the work
Getting the Current Date into a Header
It's a very common practice to put the run date in the header of a report It's such a common practice, in fact, that it's a wonder Oracle does not provide a convenient way to do it Unlike the case with the page number, which SQL*Plus conveniently maintains in the SQL.PNO user variable, the date is not so easily available How then, do you display the date in the page header? The answer is to retrieve it from the database, put it into a user variable, and display that
variable in the header
You saw how to display the contents of a user variable in the header when the Project Hours and Dollars Detail report was converted to a master/detail style You also saw how to use the COLUMN command to tell SQL*Plus to
continuously update the contents of a user variable with the value of a column in the query Getting the system date to display in the header involves a little trick that takes advantage of this use of the COLUMN command The trick is to execute a query