< previous page page_97 next page > Page 97 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; 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. < previous page page_97 next page > < previous page page_98 next page > Page 98 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. < previous page page_98 next page > < previous page page_99 next page > Page 99 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 < previous page page_99 next page > < previous page page_100 next page > Page 100 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 < previous page page_100 next page > < previous page page_101 next page > Page 101 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 < previous page page_101 next page > < previous page page_102 next page > Page 102 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 < previous page page_102 next page > < previous page page_103 next page > Page 103 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 < previous page page_103 next page > < previous page page_104 next page > Page 104 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 < previous page page_104 next page > < previous page page_105 next page > Page 105 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 ====================================================================== < previous page page_105 next page > < previous page page_106 next page > Page 106 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 < previous page page_106 next page > . 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. 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. 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