2011/12/29

Apache POI によるエクセルファイルの出力 その1 (Export to Excel file via Apache POI, Part 1)

以前、C#でエクセル形式のファイルへの出力を検証しました。今回はJava環境での検証を行います。(「エクセルファイルのバッチ出力 その1」を併せて参照してください)。

【概要】
全体の概要は以前の検証と変更ありません。参考まで、概要図を再掲します。
Figure 1: Overall image

【環境】
サンプルはJavaで実装します。そのほかの環境は以下の通りです。
  • 使用ライブラリ: Apache POI 3.7 (XSSF)
  • Excel: Excel2010
  • データベース:Oracle Database 11gR2 (11.2.0.1)
  • DB接続:Oracle JDBC Thinドライバ

【ソース(本体部)】
ソースの例を以下に示します。データの抽出、およびエクセルへの出力を定義します。
この例では、レコードセットをnextで移動する毎に、テンプレートの行をコピーおよび挿入し、セル毎にデータを挿入します。

List 1: Main logic


package util;

import java.io.*;
import java.util.Iterator;
import java.sql.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;


public class Ora2Excel {

  // db objects
  private Connection con;
  private Statement  stm;
  
  // poi objects
  private Workbook wbk;
  
  public Ora2Excel() throws SQLException {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
  }

  public void openDb(String userId, String password, String connString) throws SQLException {
    con = DriverManager.getConnection(connString, userId, password);
    stm = con.createStatement();
  }
  
  public void closeDb() throws SQLException {
    stm.close();
    con.close();
  }
  
  public void openBook(String fileName) throws IOException {
    wbk = new XSSFWorkbook(fileName);
  }
  
  public void saveBook(String fileName) throws IOException {
    FileOutputStream out = new FileOutputStream(fileName);
    wbk.write(out);
    out.close();
  }
  
  public void closeBook() {
    wbk = null;
  }
  
  public void extract(String sheetName, String sql) throws SQLException {
    Sheet wsh = wbk.getSheet(sheetName);
    ResultSet rst = stm.executeQuery(sql);
    int colCount = rst.getMetaData().getColumnCount();
    
    // determine the start position: search "$start"
    int rowStart = 0;
    int colStart = 0;
    Iterator<Row> iRow = wsh.rowIterator();
    while (iRow.hasNext() && rowStart + colStart == 0) {
      Row currentRow = (Row) iRow.next();
      Iterator<Cell> iCol = currentRow.cellIterator();
      while (iCol.hasNext()) {
        Cell currentCell = (Cell) iCol.next();
        if (currentCell.getCellType() == Cell.CELL_TYPE_STRING  && currentCell.getStringCellValue().trim().equalsIgnoreCase("$start")) {
          rowStart = currentCell.getRowIndex();
          colStart = currentCell.getColumnIndex();
          break;
        }
      }
    }
    
    // get "template row"
    Row templateRow = wsh.getRow(rowStart);
    
    // set cell values
    int idxRow = rowStart;
    while (rst.next()) {
      wsh.shiftRows(idxRow, wsh.getLastRowNum()+1, 1);
      
      Row r = wsh.createRow(idxRow);
      for (int idxCol = templateRow.getFirstCellNum(); idxCol < templateRow.getLastCellNum(); idxCol++) {
        Cell c = r.createCell(idxCol);
        
        if (idxCol >= colStart && idxCol - colStart < colCount) {
          int idxDbCol = idxCol-colStart + 1;
          switch(rst.getMetaData().getColumnType(idxDbCol)){
          case Types.NUMERIC:
            c.setCellValue(rst.getDouble(idxDbCol));
            break;
          case Types.DATE:
              c.setCellValue(rst.getDate(idxDbCol));
            break;
          case Types.TIMESTAMP:
              c.setCellValue(rst.getDate(idxDbCol));
            break;
          default:
            c.setCellValue(rst.getString(idxDbCol));
          }
        } else if (templateRow.getCell(idxCol).getCellType() == Cell.CELL_TYPE_FORMULA){
          c.setCellFormula(templateRow.getCell(idxCol).getCellFormula());
        } else if (templateRow.getCell(idxCol).getCellType() == Cell.CELL_TYPE_NUMERIC){
          c.setCellValue(templateRow.getCell(idxCol).getNumericCellValue());
        } else if (templateRow.getCell(idxCol).getCellType() == Cell.CELL_TYPE_STRING){
          c.setCellValue(templateRow.getCell(idxCol).getStringCellValue());
        } 
        c.setCellStyle(templateRow.getCell(idxCol).getCellStyle());
        
      }
      idxRow++;
    }
    rst.close();
    
    // remove the template row.
    wsh.removeRow(templateRow);
    wsh.shiftRows(idxRow,  wsh.getLastRowNum()+1, -1);
    
    // calculate formula cells
    XSSFFormulaEvaluator.evaluateAllFormulaCells((XSSFWorkbook) wbk);
  }
}

【テスト用ソース(呼び出し部)】
上記クラスを呼び出す側の例を以下に示します。


List 2: Calling Main Logic


import util.Ora2Excel;

public class Test1 {

  public static void main(String[] args) throws Exception {
    Ora2Excel o2x = new Ora2Excel();
    
    o2x.openDb("scott", "tiger", "jdbc:oracle:thin:@windows2003srv:1521:orcl");
    
    o2x.openBook("c:\\template.xlsx");
    o2x.extract("Sheet1", "select deptno, empno, ename, job, mgr, hiredate, sal, comm from emp order by deptno, empno");
    o2x.saveBook("c:\\result.xlsx");
    o2x.closeBook();
    
    o2x.closeDb();
  }
}


【テンプレートファイル】
テンプレートファイルは、前回使用したファイルからスパークラインを除去したものを使用しています(詳細はダウンロードしたファイルの内容を参照して下さい)。
※現状ではApache POIがスパークラインに対応していないため。

データを出力する開始点に"$start"を入力します。
Figure 2: Template file

出力結果のイメージは以下の通りです。
Figure 3: Result


次回からは性能検証、および性能改善の方策を検討します。

エクセルのテンプレートファイル、結果ファイルはここからダウンロードできます。
※上記のソースはサンプルです。例外処理などは除外しています。
※上記のソースおよびファイルを使用したことによる一切の結果について、作成者は責任を負いません。

[Summary]
The sample above shows how to export data onto Excel sheet via Apache POI (former Jakarta POI).
cf. Excel file processing, Part 1


[Outline]
Overall image is shown in Figure 1 - same as the past post.


[Environment]
Source is in Java. Environment are as follows:
  • External Library: Apache POI 3.7
  • Excel: Excel2010
  • Database: Oracle Database 11gR2 (11.2.0.1)
  • DB connection: Oracle JDBC Thin Driver

[Source (main part)]
Please see List 1.  The sample class retrieves rows from database, and set the data onto workbook cell by cell.


[Test source]
Please see List 2.


[Template file]
Please see Figure 2 (Template file) and 3 (Output).  In the template, you set the report header, number format, formula and so on.  The start position, the top-left position, of data area is defined by "$start".  Please refer to the sample file for details.

2011/12/26

ファンクション・ポイントにおける言語生産性 (Function Point: Language Productivity)

前回は Oracle BI Publisher の生産性について記載しました。
今回は参考まで、一般的な開発言語に関する比較的新しい情報が以下に公開されていましたのでご紹介します。

財団法人経済調査会
http://www.zai-keicho.com/

この中の「平成21年度 ソフトウェア開発に関する調査票(受託者向け) 集計結果その1」に、いくつかの言語の生産性について集計結果が掲載されています。


内容に関しては、サンプル数が十分ではないことに起因すると考えられるばらつきが散見されますが、言語生産性以外の集計の視点も参考になり、貴重な調査結果であると思います。「集計結果その2」にも、開発要員数と生産性の関連など、承認者の説得に困った際のピンポイントの材料として参照できる内容が記載されています。

※このブログと財団法人経済調査会とは無関係です。
※ファンクション・ポイント法を推奨する目的の記事(ブログ)ではありません。あくまでも参考指標としてご認識ください。


[Summary]
No English translation on this post.  please visit Google Translate.

2011/12/20

BI Publisherの生産性 (Productivity of Oracle BI Publisher)


Oracle BI Publisherに限らず、一般的な帳票ツールの導入が開発の効率を向上させることはよほどの間違いがない限り確実といってよいと思われます。しかしながら、どの程度の効果があるかという点については明確な答えがありません。
今回はファンクション・ポイント(FP: Function Point)の算出におけるOracle BI Publisherの言語生産性について考察します。
※記事の中の値および計算過程はいかなる効果も保証しません。また、VAF(調整係数)は考慮しません。

【帳票のファンクション・ポイント】
今回の記事では帳票を中程度の外部出力(EO: External Output)として平均化し、ファンクション・ポイントを5として見積もります。詳細は以下のURLを参照してください(英語)。
http://www.devdaily.com/FunctionPoints/


【開発工数】
開発工数の見積もりは、設計書やテストの粒度をどの程度想定するかに大きく依存します。
BI Publisherを採用した場合、標準的な集計表であれば実装(コーディング)そのものは1人日以下で対応可能と想定できます。
実装以外の設計やテスト、およびそれらのレビュー、オーバーヘッドに関わる工数はプロジェクトのポリシーに従ってゼロから4人日程度を想定します。


【言語生産性の算出】
1か月の稼働は20日ですので、1人月で開発可能な帳票の数は以下の様に見積もり算出されます。
  • 実装のみを見積もるパターン: 20days / (1day + 0day) = 20reports
  • 設計やテストに厚みを想定するパターン: 20days / (1day + 4days) = 4reports

これらの値を単純に掛け合わせ、言語生産性を算出します。
  • 実装のみを見積もるパターン: 20reports * 5points = 100points
  • 設計やテストに厚みを想定するパターン: 4reports * 5points = 20points
「詰め込めば何とか開発できる」という帳票ツールの性質が数値に現れたとも言えます(あまり良いことではありませんが)。


【補足】
ファンクション・ポイントに関わる係数は担当者の練度や対象システムの性質、組織の文化に大きく依存します。本記事で算出された値はあくまでもサンプルであり、実際の見積もりにおいては係数を個別に見直す必要があります。


[Summary]
Calculate the Productivity of Oracle BI Publisher.


[Function point of a report]
Consider all report as External Output (EO) with medium difficulty: Its FP is defined as 5 points.
(*) For more details, please refer to the following URL:
http://www.devdaily.com/FunctionPoints/


[Development Volume]
The Development Volume depends on how much you contribute supplemental tasks - such as documents, review and testing.
With BI Publisher, you can develop an ordinary report in a day or less.
For the supplemental takes, around zero to four days, depend on your project policy.


[Productivity]
The numbers of reports that you can implement in a month (20 days):
  • Maximum performance (without supplemental takes ): 20days / (1day + 0day) = 20reports
  • With supplemental takes: 20days / (1day + 4days) = 4reports
The productivity is:
  • Maximum performance (without  supplemental takes): 20reports * 5points = 100points
  • With supplemental takes: 4reports * 5points = 25points


[Note]
The values above do not guarantee accuracy.  VAF is excluded.  Please find your own values.

2011/12/07

SQLによるクロス集計 (SQL: Cross tab)

SQLで小計と合計を取得する (SQL: Subtotal and Grand Total)」では小計と合計を取得するSQLについて説明しました。
今回はクロス集計を行う方法について確認します。

【sum とdecodeによる集計】
古典的な方法は以下の様にsumdecodeを組み合わせる構文です。

List 1

select prd.prod_id, prd.prod_name, sum(decode(extract(month from sls.time_id), 1, sls.quantity_sold)) as JAN, sum(decode(extract(month from sls.time_id), 2, sls.quantity_sold)) as FEB, sum(decode(extract(month from sls.time_id), 3, sls.quantity_sold)) as MAR, sum(decode(extract(month from sls.time_id), 4, sls.quantity_sold)) as APR, sum(decode(extract(month from sls.time_id), 5, sls.quantity_sold)) as MAY, sum(decode(extract(month from sls.time_id), 6, sls.quantity_sold)) as JUN, sum(decode(extract(month from sls.time_id), 7, sls.quantity_sold)) as JUL, sum(decode(extract(month from sls.time_id), 8, sls.quantity_sold)) as AUG, sum(decode(extract(month from sls.time_id), 9, sls.quantity_sold)) as SEP, sum(decode(extract(month from sls.time_id), 10, sls.quantity_sold)) as OCT, sum(decode(extract(month from sls.time_id), 11, sls.quantity_sold)) as NOV, sum(decode(extract(month from sls.time_id), 12, sls.quantity_sold)) as DEC, sum(quantity_sold) as subtotal from sales sls, products prd where sls.prod_id = prd.prod_id and sls.time_id between to_date('19980101','yyyymmdd') and to_date('19981231','yyyymmdd') group by prd.prod_id, prd.prod_name order by prd.prod_id; 

出力結果は以下の通りです。
Figure 1: sum and decode

この例では結果セットがそのままクロス集計表の形式になっているため、結果セットを扱う側のプログラム処理が簡単であるという長所があります。
この構文は横軸(列)の構成が年月の場合など、固定している場合には有用です。


【pivot】
Oracle Database 11g から、新たにpivot句が導入されました。
以下のSQLは前述のSQLと同等です。仕様によるものかは不明ですが、pivotを行う前に、対象となるデータを副問い合わせで絞り込んでおく必要があるようです。

List 2


select *
  from (select prd.prod_id,
               prd.prod_name,
               extract(month from sls.time_id) time_id,
               sls.quantity_sold from sales sls, products prd
         where sls.prod_id = prd.prod_id
           and sls.time_id between to_date('19980101','yyyymmdd') and to_date('19981231','yyyymmdd'))
 pivot (sum(quantity_sold) for time_id in (1,2,3))
 order by prod_id;

出力結果はFigure 1を参照してください。

残念ながら、現状ではpivot句のinに指定する値をサブクエリーで指定することができず、固定値として指定しなければならないため、この部分を動的SQLで組み立てる等の作り込が必要です。
この辺りには機能仕様の詰めの甘さを感じます。将来対応を期待する箇所です。


【group by cube()の使用】
group by cube()は、クロス集計結果を戻します。列の構成はselect句の指定のままですので、結果セットを受け取ったプログラム側でクロス集計表形式に展開する必要がありますが、前述の2つの方式と異なり、横軸の要素が事前に不明である場合でも、すべての組み合わせを自動で集計する点で優位です。
group by cube()は2つ以上のディメンジョンを同時に計算することができます。以下の例では、一般的な2次元の集計に、product表を結合してproduct.prod_nameを表示しています。

List 3



select prd.prod_id,
       prd.prod_name,
       sls.mm,
       sls.sum_quantity
  from products prd,
       (
        select prod_id,
               extract(month from time_id) as mm,
               sum(quantity_sold) as sum_quantity
          from sales
         where time_id between to_date('19980101','yyyymmdd') and to_date('19981231','yyyymmdd')
         group by cube (prod_id, extract(month from time_id))
       ) sls
 where prd.prod_id(+) = sls.prod_id
 order by prd.prod_id, sls.mm;

出力結果は以下の通りです。
Figure 2: group by cube()



[Summary]
There are three ways to perform cross tabulation.
[sum and decode]
The traditional way is List1. Figure 1 shows its result.

[pivot]
Oracle Database 11g releases pivod operator. With pivot, you can rewrite the previous SQL very simple. Please see List 2.
Unfortunately, you need to specify in clause with static values - in clause does not accept sub query. We hope this function will be improved in the future release.

[group by cube()]
group by cube() produces cross-tabulation values. Since it does not format its results like spread sheet, it is your application's duty to format spread sheet like output. Please see List 3 and Figure 2.

(*) for group by rollup(), please refer the past post here.

2011/11/24

出力形式の表示順 (Order of output format list)


OTNに「出力形式の順序を変更する方法」が質問されていました。
https://forums.oracle.com/forums/thread.jspa?threadID=514121

スレッドの中で、BI Publisher 10gでの変更方法と思われる回答が寄せられていました。これをヒントに、BI Publisher 11gで表示順序が変更ができるかを検証します。

【変更前の状態】
変更前の状態は以下の通りです。
Figure 1: Original order

【ダウンロード】
対象となるレポートを選択し、ダウンロードします。
Figure 2: Download report

【解凍】
ダウンロードした *.xdozファイルをZIPファイルとして解凍します。

【定義の編集】
解凍されたファイルに含まれる_report.xdoを開きます。
List 1:
<?xml version = '1.0' encoding = 'utf-8'?> <report xmlns="http://xmlns.oracle.com/oxp/xmlp" xmlns:xsd="http://wwww.w3.org/2001/XMLSchema" version="2.0" dataModel="true" useBipParameters="true" producerName="fin-financialCommon" parameterVOName="" parameterTaskFlow="" customDataControl="" cachePerUser="true" cacheSmartRefresh="false" cacheUserRefresh="false"> <dataModel url="/~weblogic/dmEmp.xdm"/> <description/> <property name="showControls" value="true"/> <property name="online" value="true"/> <property name="openLinkInNewWindow" value="true"/> <property name="autoRun" value="false"/> <property name="cacheDocument" value="true"/> <property name="showReportLinks" value="true"/> <property name="asynchronousRun" value="false"/> <property name="useExcelProcessor" value="false"/> <property name="cacheDuration" value="30"/> <property name="controledByExtApp" value="false"/> <parameters paramPerLine="3"/> <templates default="layout1"> <template label="layout1" url="layout1.rtf" type="rtf" outputFormat="html,pdf,rtf,excel,xlsx,pptx,csv" defaultFormat="html" locale="en_US" disableMasterTemplate="true" active="true" viewOnline="false"/> </templates> </report> 

下線部を編集します。
List 2:
<templates default="layout1"> <template label="layout1" url="layout1.rtf" type="rtf" outputFormat="csv,pptx,xlsx,excel,rtf,pdf,html" defaultFormat="html" locale="en_US" disableMasterTemplate="true" active="true" viewOnline="false"/> </templates>

【圧縮】
編集後、ファイルを再度ZIP形式で圧縮し、拡張子を.xdozに変更します。


【アップロード】
ファイルをアップロードします。
Figure 3: Upload

【結果】
出力形式の表示順が_report.xdoで指定した順に変わっていることが確認できます。
Figure 4: Result - order changed.

なお、残念ながら、変更後にレポートの定義を編集・保存すると表示順は元に戻ってしまいます。


[summary]


OTN thread: Default output format
https://forums.oracle.com/forums/thread.jspa?threadID=514121


The solution on BI Publisher 10g is posted on the thread above.  I've checked if this solution works on BI Publisher 11g as well.
You can change the order of output format with the following step:


1) Download the report (See Figure 2).
2) Unzip the download file (*.xdoz).
3) Edit _report.xdo (See List 1 and 2).
4) Zip the files again, and upload it onto BI Publisher (Figure 3).


Note that the order of output format will be reset when you edit and save the report.

2011/11/21

SCOTT スキーマ (SCOTT schema)

SCOTTって何ですか」という質問がありました。このブログではサンプルの説明には特にことわりもなくSCOTTスキーマを使用してきましたが、言われてみれば、古くからOracle DBを使用されている方でない限り、知る由のないスキーマではあります。

昔日の頃、プログラム言語を学ぶ際に最初に表示する文字列が「Hello World!」であるのと同じく、Oracle DBを学ぶ際に最初に使用するサンプルスキーマはSCOTTスキーマでした。
現行バージョン(Oracle 11gR2)でも %ORACLE_HOME%/rdbms/admin/utlsampl.sql で作成することができます。

ER図は以下のPDFで参照することができます。
http://www.datamasker.com/DMO/HelpFiles/ScottERDiagram.pdf


[Summary]
I've got a question "What is SCOTT?" - now I know SCOTT is no longer popular for the engineers under 30.
Young people, please see the file %ORACLE_HOME%/rdbms/admin/utlsampl.sql.


ER diagram is available:
http://www.datamasker.com/DMO/HelpFiles/ScottERDiagram.pdf

2011/11/17

SQLで小計と合計を取得する:補足 (SQL: Subtotal and Grand Total: Appendix)

前回の記事で、処理性能の検証結果に「非効率なSQLの例」を載せ忘れていましたので掲載します。
また、group by および group by rollup()の例も併せて再掲します。

List 8:
********************************************************************************

select *
  from (
       select deptno, job, sum(sal) sum_sal
         from emp2
        group by deptno, job
       union
       select deptno, null as job, sum(sal) sum_sal
         from emp2
        group by deptno
       union
       select null as deptno, null as job, sum(sal) sum_sal
         from emp2
       )
 order by deptno, job

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      1.10       1.41      25695      50853          0          13
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      1.10       1.43      25695      50853          0          13

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 86  

Rows     Row Source Operation
-------  ---------------------------------------------------
     13  SORT ORDER BY (cr=50853 pr=25695 pw=0 time=0 us cost=1083 size=480 card=15)
     13   VIEW  (cr=50853 pr=25695 pw=0 time=120 us cost=1082 size=480 card=15)
     13    SORT UNIQUE (cr=50853 pr=25695 pw=0 time=36 us cost=1082 size=190 card=15)
     13     UNION-ALL  (cr=50853 pr=25695 pw=0 time=192 us)
      9      HASH GROUP BY (cr=16951 pr=8565 pw=0 time=32 us cost=248 size=165 card=11)
1200000       TABLE ACCESS FULL EMP2 (cr=16951 pr=8565 pw=0 time=2393598 us cost=239 size=1800000 card=120000)
      3      HASH GROUP BY (cr=16951 pr=8565 pw=0 time=202 us cost=248 size=21 card=3)
1200000       TABLE ACCESS FULL EMP2 (cr=16951 pr=8565 pw=0 time=2320510 us cost=239 size=840000 card=120000)
      1      SORT AGGREGATE (cr=16951 pr=8565 pw=0 time=0 us cost=587 size=4 card=1)
1200000       TABLE ACCESS FULL EMP2 (cr=16951 pr=8565 pw=0 time=1736062 us cost=239 size=480000 card=120000)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  direct path read                                8        0.00          0.01
  asynch descriptor resize                        5        0.00          0.00
  SQL*Net message from client                     2        0.01          0.01
********************************************************************************

select deptno, job, sum(sal) sum_sal
  from emp2
 group by deptno, job
 order by
       deptno, job

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.54       0.53         15       8580          0           9
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.54       0.53         15       8580          0           9

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 86  

Rows     Row Source Operation
-------  ---------------------------------------------------
      9  SORT GROUP BY (cr=8580 pr=15 pw=0 time=0 us cost=284 size=135 card=9)
1200000   TABLE ACCESS FULL EMP2 (cr=8580 pr=15 pw=0 time=2297470 us cost=245 size=18000000 card=1200000)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  db file sequential read                        15        0.00          0.00
  SQL*Net message from client                     2        0.00          0.00
********************************************************************************

select deptno, job, sum(sal) sum_sal
  from emp2
 group by rollup (deptno, job)
 order by
       deptno, job

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.54       0.56         17       8580          0          13
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.54       0.56         17       8580          0          13

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 86  

Rows     Row Source Operation
-------  ---------------------------------------------------
     13  SORT GROUP BY ROLLUP (cr=8580 pr=17 pw=0 time=0 us cost=284 size=195 card=13)
1200000   TABLE ACCESS FULL EMP2 (cr=8580 pr=17 pw=0 time=2089726 us cost=245 size=18000000 card=1200000)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  db file sequential read                        17        0.00          0.00
  SQL*Net message from client                     2        0.01          0.01





[Summary]
I forgot to put the trece of Bad Example in the last post.
Here List 8 shows the results of three SQL - bad example, group by and group by rollup.