月別アーカイブ: 2014年8月

NULLを排除した設計②

先日投稿した「NULLを排除した設計①」の続きです。

若干変更

前回の投稿から若干変更です。せっかくCLOBカラムを定義したので、I/O負荷を高くするため値を設定する際に4000バイト長のデータにして格納することにしました。(LOBカラムへの正規の値格納方法ではないのですが、簡単に4000バイトのデータを設定しました。)

SQL> update CUSTOMERS_1 set REMARKS = rpad('0',4000,'0') where mod(CUST_ID,5 ) = 0;

10000行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = rpad('1',4000,'1') where mod(CUST_ID,10) = 1;

5000行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = rpad('2',4000,'2') where mod(CUST_ID,20) = 2;

2500行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = rpad('3',4000,'3') where mod(CUST_ID,50) = 3;

1000行が更新されました。

SQL> commit;

コミットが完了しました。

SQL> select
  2   CUST_ID
  3  ,nvl(DBMS_LOB.SUBSTR(REMARKS, 10, 1),'Null') REMARKS
  4  ,DBMS_LOB.GETLENGTH(REMARKS) LENGTH
  5  from CUSTOMERS_1 where CUST_ID < 21
  6  order by CUST_ID;

   CUST_ID REMARKS             LENGTH
---------- --------------- ----------
         1 1111111111            4000
         2 2222222222            4000
         3 3333333333            4000
         4 Null
         5 0000000000            4000
         6 Null
         7 Null
         8 Null
         9 Null
        10 0000000000            4000
        11 1111111111            4000

   CUST_ID REMARKS             LENGTH
---------- --------------- ----------
        12 Null
        13 Null
        14 Null
        15 0000000000            4000
        16 Null
        17 Null
        18 Null
        19 Null
        20 0000000000            4000

20行が選択されました。

SQL> select
  2   nvl(DBMS_LOB.SUBSTR(REMARKS, 10, 1),'Null') REMARKS
  3  ,count(*)
  4  from
  5   CUSTOMERS_1
  6  group by
  7   DBMS_LOB.SUBSTR(REMARKS, 10, 1)
  8  order by
  9   1;

REMARKS           COUNT(*)
--------------- ----------
0000000000           10000
1111111111            5000
2222222222            2500
3333333333            1000
Null                 31500

パフォーマンス比較

テストは同じSQL文をそれぞれ3回連続で実行し、一番経過時間が短かった結果を採用しました。

まずは全件検索(結果:50,000件)

①-1 単純カラム追加(NULLを排除しない設計)

SQL> select
  2   CUST_ID
  3  ,CUST_FNAME
  4  ,CUST_LNAME
  5  ,SEX
  6  ,REMARKS
  7  from
  8   CUSTOMERS_1;

50000行が選択されました。

 経過: 00:00:30.41

実行計画
----------------------------------------------------------
Plan hash value: 2813274236

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             | 50000 |  3857K|   140   (0)| 00:00:02 |
|   1 |  TABLE ACCESS FULL| CUSTOMERS_1 | 50000 |  3857K|   140   (0)| 00:00:02 |
---------------------------------------------------------------------------------

統計
----------------------------------------------------------
       0  recursive calls
       0  db block gets
  198074  consistent gets
  148000  physical reads
       0  redo size
24824948  bytes sent via SQL*Net to client
17644472  bytes received via SQL*Net from client
  124002  SQL*Net roundtrips to/from client
       0  sorts (memory)
       0  sorts (disk)
   50000  rows processed

②-1 テーブル追加(Nullを排除した設計)

SQL> select
  2   C.CUST_ID
  3  ,C.CUST_FNAME
  4  ,C.CUST_LNAME
  5  ,C.SEX
  6  ,R.REMARKS
  7  from
  8   CUSTOMERS_2 C left outer join CUST_2_REMARKS R
  9  on C.CUST_ID = R.CUST_ID;

50000行が選択されました。

 経過: 00:00:30.31

実行計画
----------------------------------------------------------
Plan hash value: 2466772454

------------------------------------------------------------------------------------------------
| Id  | Operation             | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                | 50000 |  7373K|       |   361   (1)| 00:00:05 |
|*  1 |  HASH JOIN RIGHT OUTER|                | 50000 |  7373K|  2248K|   361   (1)| 00:00:05 |
|   2 |   TABLE ACCESS FULL   | CUST_2_REMARKS | 18500 |  2023K|       |    40   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL   | CUSTOMERS_2    | 50000 |  1904K|       |    90   (0)| 00:00:02 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("C"."CUST_ID"="R"."CUST_ID"(+))

統計
----------------------------------------------------------
       0  recursive calls
       0  db block gets
  198137  consistent gets
  148000  physical reads
       0  redo size
24824948  bytes sent via SQL*Net to client
17644472  bytes received via SQL*Net from client
  124002  SQL*Net roundtrips to/from client
       1  sorts (memory)
       0  sorts (disk)
   50000  rows processed

このクエリーはOUTER JOINで書くのがポイントです。

コスト値は単純カラム追加の方が半分以下なのですが、physical readsは両者全く同じで、経過時間もほとんど同じです。

②のコストが大きいのはハッシュ・ジョインのためですが、パフォーマンス的には遜色のない結果となっています。

IS NOT NULL検索はどうか?(結果:18,500件)

①-2 単純カラム追加(NULLを排除しない設計)


SQL> select
  2   CUST_ID
  3  ,CUST_FNAME
  4  ,CUST_LNAME
  5  ,SEX
  6  ,REMARKS
  7  from
  8   CUSTOMERS_1
  9  where REMARKS is not null;

18500行が選択されました。

経過: 00:00:24.06

実行計画
----------------------------------------------------------
Plan hash value: 2813274236

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             | 18500 |  1427K|   143   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| CUSTOMERS_1 | 18500 |  1427K|   143   (2)| 00:00:02 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("REMARKS" IS NOT NULL)

統計
----------------------------------------------------------
       0  recursive calls
       0  db block gets
  166771  consistent gets
  148000  physical reads
       0  redo size
20641347  bytes sent via SQL*Net to client
17297972  bytes received via SQL*Net from client
   92502  SQL*Net roundtrips to/from client
       0  sorts (memory)
       0  sorts (disk)
   18500  rows processed

②-2 テーブル追加(Nullを排除した設計)

SQL> select
  2   C.CUST_ID
  3  ,C.CUST_FNAME
  4  ,C.CUST_LNAME
  5  ,C.SEX
  6  ,R.REMARKS
  7  from
  8   CUSTOMERS_2 C inner join CUST_2_REMARKS R
  9  on C.CUST_ID = R.CUST_ID;

18500行が選択されました。

経過: 00:00:23.60

実行計画
----------------------------------------------------------
Plan hash value: 2209842270

---------------------------------------------------------------------------------------------
| Id  | Operation          | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                | 18500 |  2728K|       |   361   (1)| 00:00:05 |
|*  1 |  HASH JOIN         |                | 18500 |  2728K|  2248K|   361   (1)| 00:00:05 |
|   2 |   TABLE ACCESS FULL| CUST_2_REMARKS | 18500 |  2023K|       |    40   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| CUSTOMERS_2    | 50000 |  1904K|       |    90   (0)| 00:00:02 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("C"."CUST_ID"="R"."CUST_ID")

統計
----------------------------------------------------------
       0  recursive calls
       0  db block gets
  166835  consistent gets
  148000  physical reads
       0  redo size
20641347  bytes sent via SQL*Net to client
17297972  bytes received via SQL*Net from client
   92502  SQL*Net roundtrips to/from client
       1  sorts (memory)
       0  sorts (disk)
   18500  rows processed

このクエリーはINNER JOINで書くのがポイントですが、全件検索と同様に②-1と較べて遜色ないというか、むしろ若干よい結果が出ました。(これは偶然だと思いますが。)

IS NULL検索はどうか?(結果:31,500件)

③-1 単純カラム追加(NULLを排除しない設計)

SQL> select
  2   CUST_ID
  3  ,CUST_FNAME
  4  ,CUST_LNAME
  5  ,SEX
  6  ,REMARKS
  7  from
  8   CUSTOMERS_1
  9  where REMARKS is null;

31500行が選択されました。

経過: 00:00:06.56

実行計画
----------------------------------------------------------
Plan hash value: 2813274236

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             | 31500 |  2430K|   143   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| CUSTOMERS_1 | 31500 |  2430K|   143   (2)| 00:00:02 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("REMARKS" IS NULL)

統計
----------------------------------------------------------
      0  recursive calls
      0  db block gets
  31745  consistent gets
      0  physical reads
      0  redo size
4184175  bytes sent via SQL*Net to client
 346972  bytes received via SQL*Net from client
  31502  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
  31500  rows processed

③-2 テーブル追加(Nullを排除した設計)


SQL> select
  2   CUST_ID
  3  ,CUST_FNAME
  4  ,CUST_LNAME
  5  ,SEX
  6  ,null REMARKS
  7  from
  8   CUSTOMERS_2
  9  where not exists (
 10      select
 11       1
 12      from
 13       CUST_2_REMARKS
 14      where CUSTOMERS_2.CUST_ID = CUST_2_REMARKS.CUST_ID
 15                   );

31500行が選択されました。

経過: 00:00:00.75

実行計画
----------------------------------------------------------
Plan hash value: 2401287113

----------------------------------------------------------------------------------------
| Id  | Operation          | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                   | 31500 |  1353K|    93   (4)| 00:00:02 |
|   1 |  NESTED LOOPS ANTI |                   | 31500 |  1353K|    93   (4)| 00:00:02 |
|   2 |   TABLE ACCESS FULL| CUSTOMERS_2       | 50000 |  1904K|    90   (0)| 00:00:02 |
|*  3 |   INDEX UNIQUE SCAN| PK_CUST_2_REMARKS |  6845 | 34225 |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("CUSTOMERS_2"."CUST_ID"="CUST_2_REMARKS"."CUST_ID")

統計
----------------------------------------------------------
      0  recursive calls
      0  db block gets
   6386  consistent gets
      0  physical reads
      0  redo size
1577675  bytes sent via SQL*Net to client
  23561  bytes received via SQL*Net from client
   2101  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
  31500  rows processed

このクエリーはNOT EXIST句を使って書くのがポイントですが、テーブル分割した方が圧倒的にパフォーマンスがよいです!

しかし、実はこれには落とし穴があります。IS NULL条件なので結果のREMARKSカラムは必ずNullになります。③-1は以下のように書き換えが可能です。

SQL> select
  2   CUST_ID
  3  ,CUST_FNAME
  4  ,CUST_LNAME
  5  ,SEX
  6  ,null REMARKS
  7  from
  8   CUSTOMERS_1
  9  where REMARKS is null;

31500行が選択されました。

経過: 00:00:00.71

実行計画
----------------------------------------------------------
Plan hash value: 2813274236

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             | 31500 |  2430K|   143   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| CUSTOMERS_1 | 31500 |  2430K|   143   (2)| 00:00:02 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("REMARKS" IS NULL)

統計
----------------------------------------------------------
      0  recursive calls
      0  db block gets
   2531  consistent gets
      0  physical reads
      0  redo size
1577675  bytes sent via SQL*Net to client
  23561  bytes received via SQL*Net from client
   2101  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
  31500  rows processed

律儀にREMARKSカラムをクエリーに入れてしまうと、パフォーマンスが極端に悪くなります。

こうして見ると設計の違いでパフォーマンスに特筆すべき差はないので、「単純なカラム追加でよいではないか。」という意見もあるかと思いますが、Nullを排除した設計の利点を次回考察していきたいと思います。

(続く)

Oracleアーキテクチャをどのように理解するか

私がDBAになったきっかけ

私は20代に某官庁で大型汎用計算機の仕事に携わった経験があったのですが、その後の異動で30代半ばまでITとは全く別の世界で生きてきました。

元々コンピュータは自分に合っているという意識があったので、民間で本格的にITに携わってみたいと転職をしました。

最初はそれまでの所属官庁と関係の深いあるSIerに入社し、そこで初めてOracleデータベースに触りました。汎用計算機時代にやっと出始めた初期のRDBMSに少し携わる機会はあったのですが、COBOLから従来型のファイルシステムの代わりにRDBMSを利用するようなシステムの運用を行っており、民間で見たOracle7 はそれとは全くの別物でした。

その民間会社で恐る恐るOracleの経験をスタートし、約2年後に当時Oracle Master保有率の日本一を争っているような会社に転職しました。

その間はずっとアプリ開発者としてのキャリアを積んでいましたが、Unixも知らなければサーバに触ることも殆ど無く、GUIの開発ツールでPL/SQLのコードをひたすら書く毎日でした。

最初はテーブルのリレーションや実行計画に関する知識もなく、結合の仕方を間違えて発生した重複行を「DISTINCT」で無理矢理1行にまとめるようなおかしなことをやっていました。今思えば赤面モノです。

資格取得に熱心な会社でしたので、転職1年後くらいにOracle Master Plutinum for Oracle 8の資格を取得しました。その後しばらくしてあるネットワークに強い運用アウトソーシング会社からDBA常駐の依頼を受けDBA人生がスタートしました。

仕組みがわかると楽しい

アプリケーションの開発経験はそこそこ積んでいましたが運用経験は皆無です。今思えば無謀な挑戦でしたが、初日にたまたまSQLチューニングを頼まれる機会があり、何とかその場で解決できたので、専門家として少しは認めてもらえました。

ただし、Unix系の経験は殆どなし、サーバやネットワークに関しても同様でしたので、最初の同僚には「思わずめまいがした。」と言われました。

でも、Oracleのスペシャリストはほとんど居なかったので、それなりに期待もされて、ある日上司から社内セミナーでOracleについて話して欲しいと頼まれました。

引き受けてみたものの、何をどう話せばよいのか皆目わからず、日本オラクル社のサイトを探していたら「Oracleアーキテクチャ」の資料がアップされているのを見つけました。

Oracleについて人前で初めて話すというプレッシャーは相当なものでしたが、下手なことはできないとまずはその資料を徹底的に読みこなすことから始めました。

REDOやUNDO(当時はRBSでしたが)、あるいはリスナー経由の接続など、(資格を取っていたにも関わらず)それまで何となくしか理解できていなかったことが改めて明確に理解でき、実際に試すことが出来る環境があったので実地の経験も積んで、初めてOracleが動く仕組みを体系的に理解出来ました。

社内Oracleセミナーは、おかげさまで大変盛況で、半日×2回の講座を希望する人が多すぎてさらに追加の講座を実施するほどでした。

インターネット創世時代から活躍されているネットワーク・エンジニアの方も参加されたのですが、データベースというのはその会社では真空状態のようで、まだまだ未熟とは言えその日から社内的にもスペシャリストとして認知してもらえたように思います。

新人は何を学ぶか?

その後新入社員研修講師や新人向け勉強会のオブザーバをする機会が沢山ありました。未経験者がどのように興味を持ってOracleアーキテクチャを学べばよいのかということについて私なりのイメージが固まってきたので以下にまとめます。

  1. UPDATE文実行の裏でどのような仕組みが動いているかを考える。SELECT、INSERT、DELETEはUPDATEがわかれば理解できる。
  2. 単純なブロック更新の仕組みから発展させ、セッションの確立から、SQL文の解析等どんどん深堀りする。
  3. ユーザ管理のバックアップを理解することでリカバリを考慮した仕組みを理解する。
  4. 自由に再起動が出来る自分専用の検証環境を準備し、参考書等のスクリプト等を実地に確かめる。
  5. プロセスをKillしたり、ファイルを壊してみたり異常状態とそこからのリカバリを確かめる。
  6. 実行計画、統計情報の読み方を覚えて、どのようにSQL文が実行されるかを考える。
  7. OracleもOSの上で動いているものだという意識でOS、ハードウェアについても知識を広げる。

1番目のUPDATE文に関する内部処理の流れをスラスラ何も見ずにホワイトボードに描くことができれば、DBAとしての基礎が確実に備わっていると思います。

いったん筋が通った理解ができれば、そこにどんどん肉付けをして自信を深めることができます。

こういう私もまだ知らないことが沢山あることを自覚していますが、少なくとも初めて「わかった!」と思った時の感動を新人の方々にも味わってもらいたいと心から思います。

OracleマニュアルはEvernoteに取り込んで使おう

HTMLだったら何でも取り込める

またしても、Evernoteネタです。

それぐらいEvernoteは仕事の必需品です。

以前、iPadでOracleマニュアルを持ち出す という記事を書いたことがあります。Oracleマニュアルは私にとって商売道具であり愛読書でもあります。

今では紙でのOracleマニュアルを見ることはなくなりましたが、PDF、HTML、最近ではePub形式のマニュアルを無料で入手できるということは、紙マニュアルの時代を知るものとして隔世の感があります。

いろいろなフォーマットの中で知りたい場所がある程度わかっているものはやはりHTML形式に軍配が上がるのではないかと思いますが、Evernote はHTMLとの相性が抜群です。

Evernote Web Clipper

私は主にGoogle Chromeをメインのブラウザとして使っているのですが、ChromeにしろFierfoxにしろ「Evernote Web Clipper」という拡張機能(エクステンション)が提供されています。(インストール方法に関してはリンク先を参照して下さい。)

Evernote Web Clipper

Evernote Web Clipperをインストールするとブラウザに象のアイコンが表示されます。

拡張機能

クリップの仕方

クリップの実際

 

私はマニュアルのクリップを以下の要領で行っています。

  1. 取り込みたいマニュアルを表示させます。この場合ローカルディスクに保存したHTMLファイルではなくOTNサイトのマニュアルをインターネット経由で表示させます。(例は、Oracle Databaseリファレンス 11gR2 の V$SEGSTAT_NAMEの箇所です。)
  2. 取り込みたい箇所をドラッグして選択状態にします。
  3. Evernote Web Clipperのアイコンをクリックすると、上の写真のように「選択範囲」にマークがついた状態でダイアログが開きます。そのまま緑の「保存」ボタンをクリックするとEvernoteの新しいノートが作成されます。ノートのタイトルはTitleタグに記述されたものが自動的に指定されます。保存先はデフォルトノートブックですが、このタイミングで任意のノートブックを指定することも可能です。
  4. 作成されたノートは下の写真のようになります。保存先ノートブックを変更したり、必要なタグ付けをします。例では「Oracle:リファレンス」と「V$ビュー」というタグを付けています。
  5. マニュアルの記述だけではわかりにくい場合は、実際のSQL文を実行した結果を貼り付けたり、字の色を変えたりして自分だけの情報としてまとめます。

ノート

Evernoteにまとめておくと(Evernote Web Clipperがインストールしてあれば)、普通にGoogle検索をすると右側に自分のEvernote内の検索結果も同時に表示してくれます。まさに「自分だけのGoogle」です!

膨大なマニュアルの中から必要な部分を抜き出し、自分だけのコメントを付けて保存しておくと、記憶が定着するのに非常に有効です。

必要な情報をサッと取り出せる、こういうインフラを日頃から整えておけば仕事にきっと役立ちます。お試し下さい!

Google検索

NULLを排除した設計①

なぜNullを排除しなければならないのか?

テーブルを設計する際、Nullについてはあまり考慮されていないように思います。主キーは当然としてもむやみにNot Null制約を定義することは、テストデータの作り難さなどの理由でむしろ避けられているかもしれません。

ただし、緻密なテーブル設計を目的とした場合、Not Null制約によってNullデータを排除することは非常に重要です。

Nullには2つの意味がある

例えば

update CUSTOMERS set REMARKS = null where CUST_ID = 25000;

というDMLであたかも「Null」という値を設定できているような印象を持ってしまいますが、それではなぜ

select * from CUSTOMERS where REMARKS = null;

ではなく、

select * from CUSTOMERS where REMARKS is null;

というSQL文にしなければならないのでしょうか?プログラマのためのSQL 第4版 ジョー・セルコ著 には次のような一節があります。

「2値論理は、ONとOFFの2つしか状態を持たないバイナリコンピュータや多くの数学分野と極めて相性がいい。しかしSQLは3値論理を採用している。すなわち、TRUE、FALSE、UNKNOWNである。UNKNOWN という値は、比較述語や他の述語の戻り値として生じるものだが、れっきとした真理値であり、データがないことの目印であるNULLとは異なる。これが、SQLにおいて(x=NULL)ではなく(x IS [NOT] NULL) と書かねばならない理由である。NULL は値でも変数でもないので、値や変数にしか適用できない「=」という述語は適用できないのだ。」(P.266)

つまり、SQLでは「状態UNKNOWN」としてのNullと、「データがないことの目印」であるNullが混在しているということが誤解を生みやすくなっています。

Nullを許すことの問題点

以下のSQLを見てみましょう。

SQL> select 1/null from dual;

    1/NULL
----------
(Null)

変数の中にNullが紛れ込むと、どんなほとんどの場合(もちろん例外もありますが)関数の結果がNullになります。

その他、外部キーの参照先にNullデータがあると参照整合性制約の定義としては問題なくても値が取得できない問題が発生します。

プログラマのためのSQL 第4版 ジョー・セルコ著 には次のような記述もあります。

「優秀なSQLプログラマならば、NULLに対処するにあたり、まずはDDLにおいてこれを排除することに全力を傾けるだろう。すべてのテーブルのすべての列に原則としてNOT NULL 制約を付与し、正当な理由がある場合にだけNULLの使用を許可する。間違っても、こういう努力をしないまま、クエリで実装依存の変換関数に頼ればいいなどと考えないでほしい。 NULLは、SQL を知らない人々を混乱させるのだ。 加えて、NULLを使うのはコストの高い行為でもある。というのも、通常、NULLはそれが現れる列の行に余計なピットフラグを持つことで実装されている。これは前述のとおり、NULLが列自身の値ではないためだ。これによって、ストレージ要件や、インデックスおよび検索条件において本来なら必要なかったことを考慮する必要がある。」(P.272)

セルコ曰く「NULLを排除した設計」とはどういうものでしょうか?

この部分は重要だと思うので、簡単な例を挙げて説明します。

顧客テーブルを例にして

どこにでもあるような顧客テーブル(CUSTOMERS)を考えてみます。

テーブル構造は以下の様な簡単なもので、50,000件のテストデータが格納されています。

顧客テーブル

性別カラムには必ず「M」または「F」の値が格納されていてそれぞれ25,000件ずつになるようにデータを作成しています。

SQL> select SEX,count(*) from CUSTOMERS group by SEX;

SEX    COUNT(*)
---- ----------
M         25000
F         25000

備考欄を追加する

ここで、顧客毎に異なる特記事項を記録する必要が生じたことを考えてみましょう。特記事項は長くなりそうなのでCLOB型で記録する必要があるものとします。

さあ、どのように変更したらよいでしょうか?

1. 単純にカラムを追加する(Nullを排除しない設計)

それでは、よくあるパターンとしてカラム追加で対応する方法を考えてみます。

元のテーブルを丸ごとコピーしたCUSTOMERS_1テーブルに、CLOB型のREMARKSカラムを追加してみましょう。

顧客テーブル1

 

SQL> desc CUSTOMERS_1
 名前             NULL?    型
 ---------------- -------- ------------------
 CUST_ID          NOT NULL NUMBER(9)
 CUST_LNAME       NOT NULL VARCHAR2(30)
 CUST_FNAME       NOT NULL VARCHAR2(30)
 SEX              NOT NULL VARCHAR2(4)

SQL> alter table CUSTOMERS_1 add (REMARKS clob);

表が変更されました。

SQL> desc CUSTOMERS_1
 名前             NULL?    型
 ---------------- -------- -------------------
 CUST_ID          NOT NULL NUMBER(9)
 CUST_LNAME       NOT NULL VARCHAR2(30)
 CUST_FNAME       NOT NULL VARCHAR2(30)
 SEX              NOT NULL VARCHAR2(4)
 REMARKS                   CLOB

他のカラムはNot Null制約が付与されていますが、追加したばかりのREMARKSカラムはNullデータを許しています。

備考欄にデータを設定する

カラムを追加しただけではデータは「Null」なので、次のUpdate文でデータを設定します。


SQL> update CUSTOMERS_1 set REMARKS = ‘0' where mod(CUST_ID,5)  = 0;

10000行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = ‘1' where mod(CUST_ID,10) = 1;

5000行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = '2' where mod(CUST_ID,20) = 2;

2500行が更新されました。

SQL> update CUSTOMERS_1 set REMARKS = '3' where mod(CUST_ID,50) = 3;

1000行が更新されました。

SQL> commit;

コミットが完了しました。

SQL> select
  2   nvl(DBMS_LOB.SUBSTR(REMARKS, 1000, 1),'Null') REMARKS
  3  ,count(*)
  4  from
  5   CUSTOMERS_1
  6  group by
  7   DBMS_LOB.SUBSTR(REMARKS, 1000, 1)
  8  order by
  9   1;

REMARKS           COUNT(*)
--------------- ----------
0                    10000
1                     5000
2                     2500
3                     1000
Null                 31500

2. Nullを排除した設計

ジョー・セルコの言う「DDLにおいてこれを排除する」とは以下の様に備考欄を別テーブルに分割した構造です。

顧客テーブル2

REMARKSカラムにはNot Null制約を定義し、Nullデータを入力できないようにします。

DDLは以下のようになります。(ここでは便宜上CUSTOMER_1表を元にデータと一緒に作成しています。)

SQL> create table CUST_2_REMARKS as select CUST_ID,REMARKS from CUSTOMERS_1 where REMARKS is not null;

表が作成されました。

SQL> alter table CUST_2_REMARKS add constraint FK_CUST_ID_2 FOREIGN KEY (CUST_ID) REFERENCES CUSTOMERS_2 (CUST_ID);

表が変更されました。

SQL> alter table CUST_2_REMARKS modify REMARKS not null;

表が変更されました。

SQL> desc CUST_2_REMARKS
 名前              NULL?    型
 ----------------- -------- -------------
 CUST_ID           NOT NULL NUMBER(9)
 REMARKS           NOT NULL CLOB

パッと見た感じ、2つのテーブルをジョインしなければならないしパフォーマンス的には不利なような気もしますが、次回は両者のパフォーマンスの違いを見てみましょう。

(続く)

ポスト・イットケースとポスト・イットとEvernote

私はふせんを持ち歩く

先日Amazonでなかなかおしゃれなポスト・イットケースを買いました。

シヤチハタ オピニ 持ち歩きふせんカバー ホワイト OPI-FC-1

開封前

開封後

四角いポスト・イットってすぐに失くしたり、糊が剥がれてバラバラになったり、角が折れてしまったりと裸で持つと何かと不便なのでちょうどいいのを探していたら見つかりました。

ポスト・イットは糊の部分を下にして

以下は使用中の写真ですが、矢印は糊の部分です。こちらを下向きにして書いて重ねていくのが最近知ったTipsです。(ポストイットは逆貼りでアウトラインプロセッサになる。

 

使用中

Evernoteにポスト・イット・ノートを作成

そして最後はEvernoteに取り込みです。iPhone でEvernote を起動してカメラでポスト・イットを撮ると、自動的にトリミングしてくれて(影も消えてる)新規ノートとして取り込んでくれます。(写真と実際が違うのは悪しからず)カメラで取り込み

かなり鮮明に読めます。手書き文字でもOCRが使えるそうです。

PostIt Evernote

Evernoteでブルーレイ・ディスクを管理する

メディアの管理はいつも大変

好きなテレビ番組を録画してあとで観るというライフスタイルはビデオレコーダーのおかげです。

VHSテープの時代は120分テープの中にいかにきっちり録画するかが大変でした。たまに120分以上の番組を録画しなければならないような場合は、3倍モードで画質を落としたりして苦労したものです。

やがてハードディスク・レコーダーが普及し、保存しておきたい番組はDVD-R、DVD-RAM、そして今ではハイビジョン映像をブルーレイ・ディスクにダビングして保存するのが当たり前の時代になっています。

VHSテープでもブルーレイ・ディスクでもそのままにしておくと中に何を録画したかわからなくなってしまうようなことはないでしょうか?これらのメディアはとにかく数が多くなることが悩みの種です。

ラベルで管理する

VHSテープでは背中にラベルを貼って手書きでタイトルを書いたりしたものですが、私は字が下手なのでパソコンでラベルを印刷したりしてました。思い通りにきれいに印刷するのは意外と面倒で「たかが」ラベルのために休日何時間もかかってしまったこともあります。

DVD 、ブルーレイ・ディスクでも同じです。高速回転するディスク面はさすがにラベルを貼ることは難しいので、サインペン等で記入するのですが、やっぱり字が下手なのは変わらないので、プリンタを買ったらついてきた印刷専用のアプリでメディアに直接印刷したりしました。

BlueRayDisk印刷

写真は2010年頃に録画したブルーレイ・ディスクですが、これ1枚印刷するだけでかなり面倒なので休日などにまとめてするのが普通でした。

そうだEvernoteがあるじゃないか!

きれいにレベルを印刷するのがだんだん面倒になってきたのと、どうせ自分や家族しか見ないのだからこんなことに時間を費やすのがもったいなく思えてきました。

Evernoteは2009年から使っていたのですが、仕事だけでなく家庭や個人的なことにもどんどん使うようになった2011年頃からメディアの管理をEvernoteでやってみようと思いつきました。

1メディア1ノートで管理する

それまでもメディアには、ブルーレイ・ディスクであれば「BD-xxx」、DVDは「DV-xxx」というルールで通し番号を振っていましたので、これをそのまま適用して管理することにしました。

Evernoteビデオ管理

今まできれいに印刷していたメディアをEvernoteの「ビデオ管理」ノートブックにどんどんノートを作って登録していきました。メディア1枚で1ノートです。

番組名などでタグを付けておくと後で例えば「カンブリア宮殿」のディスクだけを抽出することができます。

新規は番号だけを手書きで

新規に登録するメディアには手書きで通し番号のみを記入します。他には何も書かないのでとてもシンプルです。

BlueRayDisk手書き

メディアの方は単に識別ができればよいのでこれだけでよいのです。

一方、Evernoteには以下のように情報をまとめておきます。(初期の頃と比べると若干の工夫が見られます。)

最新ビデオ管理

番組情報もEvernoteで管理

Evernoteのいいところはこれだけではなくて、別のノートへのリンクを貼り付けられることです。

例えば、2014.8.14回のカンブリア宮殿のリンクをクリックすると番組ホームページから取り込んだ以下のノートが表示されます。

番組情報

DVD、ブルーレイ・ディスクを今まで170枚くらい管理していますが、キーワードやタグで見たいディスクをすぐに検索できる仕組みができたので、ストレスのないビデオライフが確立できたと思います。

Fitbit Zipはなかなかイイぞ!

Fitbit Zipとは

Fitbit Zip

今まで使っていた歩数計を洗濯機で洗って壊してしまったので、2週間前に前から気になっていたFitbit Zipをビックカメラで買いました。

Fitbitではさらに高機能(例えば階段で登った段数がわかる、睡眠記録等)のFitbit Oneという製品があるのですが、Oneが充電式であるのに対しZipは水銀電池式で管理が楽なこと、追加機能は特に必要がないと思ったので、Zipにしました。

歩数計の場合、出かける際ズボンの左のポケットにクリップでとめてから外出し、帰ってきたら外すような使い方をしていました。ところが帰ってきてズボンをすぐに洗濯機に入れるような場合、うっかり外し忘れるとそのまま一緒に洗ってしまうことが多々ありました。(過去何台も壊してしまいました。)

つい最近まで使っていたオムロンの歩数計は防水機能がけっこうしっかりしていて、うっかり洗濯しても平気でしたが、ある日ついに水漏れで全く表示がされなくなってしまいました。

それから、オムロンのはいったん本体のボタンを長押しすることでデータ転送モードにしMacにUSB接続した専用読み取り機の上に置くことでデータ転送をしてくれたのですが、これが意外と面倒なので何日もデータを取り込まずに放置しているようなことがよくありました。

Fitbit Zipの良い所

今日でZipを使い始めてちょうど2週間ですが、今まで使ってきた歩数計とはかなり違った製品で非常に気に入っています。

気軽に付けられる

まず、「これから身につけるぞ!」という意識を持たなくてもよい気軽さです。最初はいろいろ試行錯誤しましたがトランクス(パンツ)のゴムの部分にクリップで固定するようにしています。重さ約16g、大きさは長さ約5cm×幅約3cm×厚さ約2cm弱くらいですのでつけているのを忘れてしまうほどです。

毎晩風呂に入る際にトランクスから外し、新しいトランクスに履き替えたらゴムの部分にはさみます。裏側のクリップ部分はゴムでコーティングされているのですが、液晶面を外側にするとクリップが肌に接するようになり上から圧迫されると少し痛いのです。なので液晶面を内側にして(クリップを外側に)装着するようにしています。液晶面はやや丸みを帯びているのでそれほど痛くありません。

生活防水機能なので、運動して汗をかいたくらいでは全く問題はありません。

寝るときもこのままなので、ほぼ24時間装着していることになります。以前は朝急いでいて歩数計を持って出るのを忘れると1日損した気分になったものですが、Fitbit Zipにしてからはそういうことがなくなったのでそれだけでもとても得した気になっています。

同期が楽

歩数計はデータを後で振り返えられることに意義があります。簡単な歩数計の場合、PC等にデータを転送する機能がないのでメモリ機能で過去の歩数を確認するしかありませんが、メモリ容量も限りがあるので古いデータからどんどん消えていってしまいます。

PC等にデータを転送する歩数計では、データを転送した後に歩数計自身の保存データが削除されるので、データで溢れてしまうようなことはないのですが、同期自体は手動で行うものがほとんどですので、それを忘れると意味がありません。

Fitbitの場合、iPhoneに専用アプリをインストールしてあれば、Bluetoothでほぼリアルタイムにデータを同期してくれます。バックグラウンドで常に同期するのではなく、液晶面を爪で軽く弾くような操作をすると同期が始まります。歩数系本体の液晶画面を見なくてもiPhoneアプリで今までの歩数を簡単に確認できますのでとても便利です。

アプリ画面

Mac(PC)のUSBポートにハードウェアキーと呼ばれる小さい部品を挿しておけば、Mac(PC)への同期も簡単です。(Wifi経由)

レポートが充実

iPhoneアプリやクラウドサービスのFitbitダッシュボード画面で1日単位の実績を確認してもよいのですが、毎週以下のようなレポートがメールで送られてきます。今週のアクティビティ

英語圏や中国語圏では、カロリー入力のためのクラウド上の食事データが充実しているので食事記録は比較的楽につけられるのですが、日本語ではデータベースがまだ準備されていないようで食事記録を手動で入力することになります。(私は食事記録は特に入力していません。)

それから、ある目標をクリアするとバッジをもらえる仕組みになっていてモチベーションを保つ工夫がしてあります。

おわりに

気軽さとサービスの充実でこの2週間は意識して歩くようにしています。外出しないで1万歩に到底届きそうにないような日には、マンションのフィットネスルームで走ることにしています。6〜7000歩は軽く稼ぐことができるので、運動する意識と機会は確実に増えていてモチベーションが上手に維持されているような感じです。

Oracle Linux 6.5 on Parallels Desktop for Mac OS

私の商売道具

私は仕事で2年前からMacBook Airを使っています。メモリとストレージを目一杯の8GB、512GBにそれぞれ増設し「Parallels Desktop for Mac OS(以下PD)」でWindows 7を共存させて使っています。スクリーンショット 2014-08-18 13.50.16

共有ネットワークモード

仕事で使うOracle環境はWindows側に構築しているのですが、Parallels Desktopのデフォルトのネットワーク環境は下図(PDマニュアルからの引用)のようになっており、Windows仮想マシンはMacOS側とネットワーク・アダプタを共有しているため(ネットワークについてはあまり詳しくないですが、ポートフォワーディングのような仕組みで実現していると思われます。)、MacOSから独立したサーバには見えていません。

共有ネットワークモード

 

すなわち、Windows側でリスナーを立ててもあくまでもWindows仮想マシンの中で閉じていますので、MacOS上のOracle Clientからの接続要求は受けられない仕組みです。

ブリッジイーサネット モード

仮想マシンを独自のIPを持ったスタンドアロン・コンピュータとして構成できないかということでいろいろ調べた結果、PDではブリッジイーサネット モードという構成が可能ということがわかりました。(PDマニュアルからの引用)

ブリッジイーサネット

これを使えば、仮想マシン上のリスナーで接続要求を受け付けられそうです。

Parallels Desktop上にOracle Linux 6.5をインストールする

この機能を見つけたので、前々からやりたかったOracle Linux 6.5 のインストールをやってみることにしました。(あくまでも検証環境ですので自己責任で行ってます。)

OL6.5上にOracle Databaseを構築して、MacOS上のOracle Client から接続するまでを目標とします。

インストーラのダウンロード

https://edelivery.oracle.com/ からOracle Linux 6.5のインストーラをダウンロードします。

ダウンロードページ

登録したアカウントでサインイン

メディアパック選択

Oracle Linux 6.5を選択

ダウンロード画面

V41362-01(.iso)をダウンロード!

仮想マシンの作成

新規作成の方法はいろいろありますが、仮想マシンリストの(+)ボタンをクリックしても追加できます。

新規仮想マシンの作成

新規仮想マシン

続行をクリック

空の仮想マシンを作成

PDではRedhat LinuxやCentOSのように正式にサポートされているディストリビューションであれば、DVDやイメージファイルを認識してインストールできますが、Oracle Linux は「その他のLinux」に分類されるため、まず空の仮想マシンを作成して後からOSをインストールする形になります。(実際はISOフィアルを認識させますが)

「ソースなしで続行する」にチェックを入れて続行をクリック

OSの選択

OSの選択ダイアログで、他のLinux>その他のLinuxカーネル2.6 を選択

名前と場所

名前は「Oracle Linux 6.5」に変更。「インストール前に構成をカスタマイズする」にチェックを入れて続行

ブリッジイーサネット

ここでブリッジイーサネット モードの構成にします。ハードウェアタブの「ネットワーク1」を選択。

種類を「共有ネットワーク」から「デフォルトのアダプタ」に変更、NICの種類は恐らくこの設定でよいはずです。

CDDVD

CD/DVD1を選択

接続先に先ほどダウンロードしたISOファイルの場所を指定します。

CPUメモリ

DBサーバなのでCUPを「2」、メモリを「2GB」に設定します。

クローズボタンをクリックして、続行をクリック

インストール開始

インストールスタート

一番上(Install or upgrade an existing system)が選択されていることを確認し(Enter)

diskfound2

Tabキーで「OK」から「Skip」にカーソルを移し(Enter)

start「Next」をクリック

言語

言語は「日本語」を選択

キーボード

キーボードは私の場合はUSキーボードを選択しました。

ストレージタイプ

ストレージタイプは「基本ストレージデバイス」を選択

デバイスの適用

ちょっと次に進むのをためらってしまうメッセージですが、あくまでも仮想マシンに割り当てられたディスク領域(PDは可変サイズのファイルが作成されます。)の適用ですので、「はい」を選択します。

ホスト名ネットワーク

ここでホスト名を指定します。(私の場合は「oraclelinux6.onefact.jp」としました。)

下の「ネットワークの設定」ボタンをクリックします。

ネットワーク定義

System eth0 を選択し、編集をクリック。

eth0

「自動接続する」にチェックを入れて適用をクリック

この後、タイムゾーンの設定とrootユーザのパスワード設定が続きます。

パーティションの作成

ストレージ構成

次にパーティション設定をディクスに書き込みます。私はデフォルト選択のままとしました。

パッケージ選択

次はサーバタイプによるパッケージの選択です。Database Serverという選択肢がありますが、これはMySQLサーバのことですので注意しましょう。

「今すぐカスタマイズ」ラジオボタンを選択して次に進みます。

デスクトップ

デスクトップ関連は「X Window System」「デスクトップ」「デスクトッププラットフォーム」「汎用デスクトップ」の4つを選択します。

アプリケーション

アプリケーションは「インターネットブラウザ」を選択します。

パッケージインストール中

インストールが始まります。上記の選択の場合パッケージ数は1047個になりました。

インストール完了

プログレスバーが進んでインストールが完了しました!右下の「再起動」ボタンをクリックしてリブートします。

インストール後作業

インストール後1

インストール後の設定を行います。進むをクリック。

ライセンス情報

ライセンス情報

ソフトウェア更新1

2

3

ユーザの作成

とりあえず全部「oracle」を入力します。

2

日付と時刻

Kdump

完了

oracleユーザでログインします。

端末

やっと完了です!図はターミナルを開いたところです。

次回はOracle Databaseのインストール・構築を行います。

書籍紹介:プログラムは技術だけでは動かない ~プログラミングで食べていくために知っておくべきこと

Kindleで即買い

この本は書店で立ち読みをして気になっていたのですが、今朝ひょんなことから著者の記事をEvernoteにクリップしていたのを読み返してみて、非常に心動かされるものがあったので、ベッドの中でiPad miniからKindleストアで即買いしてしまいました。

プログラムは技術だけでは動かないプログラムは技術だけでは動かない ~プログラミングで食べていくために知っておくべきこと [Kindle版]

私のハイライト

Kindle

Kindleというのは本当に便利です。最初自宅ではiPad miniのKindleアプリで読んで、午後から買い物に出かけたついでにスタバでKindle Paperwhiteで続きを読んで、気がついてみたら10章あるうちの9章までを一気に読んでいました。

IMG_5333

Kindleは読んで「いいね」と思った箇所にハイライトを付けることができて、あとから自宅のMacでハイライトした部分をまとめて

https://kindle.amazon.co.jp/your_highlights

のページで見返すことができます。(自分のアカウントでログインする必要があります。)

ちなみに私が付けたハイライトは以下の34箇所です。(数字は読書位置)

  • どれだけ立派なプログラムを開発しても、だれにも使われなければ、作った意味があるのかな? 215
  •  仕事としてのプログラミングとは、「技術自慢をする」のではなく「依頼者・利用者の要求を満たしてあげること」 221
  •  「小俣さんは技術力があるのに、きちんと話ができて、とてもめずらしい人だ」 245
  •  オタク度の高いプログラマに仕事を頼むと「意外と時間がかかる」「そもそも仕上がらない」「融通が利かない」と感じることが多いのです 259
  •  理想が高すぎるのが足を引っ張っている 262
  •  「仕様を満たすためのプログラム構造・データ構造が完璧に固められないと、プログラミングに着手できない 262
  •  行きすぎた共通化が原因 282
  •  凝りすぎた構造は、ほかの人がなかなか理解できず、当人以外はメンテナンスできないという心配も高くなります 290
  •  プログラマとして重要なのは、「依頼者の課題を解決できているかどうか」 298
  •  動くものを見ると、意外と違う意見も出てくるものです 326
  •  こちら側のリーダーが「それならお前に30分やるから、真のJavaと言えるレベルで作れ!」と言い、「かんたんなことだ」となりました。 361
  •  「始めたことはやめない」という意地 378
  •  「何が何でも1日1本分を書き続ける」ほうが重要 381
  •  「開発は引き受けましょう。そのかわり、ドキュメントはこちらの分もそちらで引き取ってもらえないでしょうか?」 417
  •  技術・仕様・分野などで「初めて」手がける際には、そもそも設計を最初から正しくできる可能性は低い 494
  •  多くの失敗プロジェクトは「技術力不足」で失敗したのではなく、「プロジェクトの進め方」に問題があったのだと感じています。 521
  •  新システムの売りである「自分でもカスタマイズ・機能追加ができる」という部分に対して、「自分でカスタマイズなどしたくない」という声が出てきました 565
  •  「自己満足ではダメで、売り手・使い手がきちんと満足できるものが正解」 615
  •  勉強会が盛り上がらない時は「プライドを傷つけられたくない」という思いが原因の1つなのだろうと感じています 679
  •  「この指摘はあなたの人格を否定しているのではなく、業務として必要なことだから」 804
  •  要するに「実績・成果で勝負しよう」と考えているのです。 814
  •  知識で競い合う必要はなく、「得た知識で何をなし得たか?」がポイントだと考えるようにすればいいのです 823
  •  意見が飛び交っている間は、まず説得はできないものだ 851
  •  好きな分野・得意な分野だから、作りたいと思えるのです。 1001
  •  「プロトタイプとして割り切る部分」と「最初からきちんと作る部分」を明確にして、本番にそのまま発展できるようにするのがお勧めです。 1021
  •  機能が多いプログラムは、開発自体も大変ですが、テストも大変です。私は基本的に、ソースを書いたらすぐにその部分の動作確認を行い、1つ1つを確実な状態として積み重ねていきます。全体を組み上げてからテストをしても、ソースの細かいところまで確認することは不可能です。書いた部分は、書いた直後が一番頭に入っていますから、すぐに動作確認するのが一番なのです。 1027
  •  すぐに作り上げるために、ソースやノウハウを蓄積しておく 1192
  •  周囲を巻き込みながら進める(特にテスト) 1194
  •  作っておしまいではなく、それをどう活用するかが大事 1196
  •  見積を要求された場合は、必ずプロトタイプで実験してから提案します。 1204
  •  こんなもの、作れない?」と知り合いから相談を受けて作ったものは「ほぼすべて」完成して、売れました。 1241
  •  IT業界には、技術や知識が豊富な人はたくさんいると感じています。しかし、技術や知識を活かして何かを生み出し、世に出している人はとても少ないと思います。 1289
  •  「相手に自分の専門性を必要とされる」 1386
  •  ・海外での実績がある製品しか選ばない ・日本企業の製品を選びたくても、メーカーが競合相手なので選べない 1786

仕事で実際にプログラムを書く機会は少なくなったのですが、上で挙げたところはIT業界で成功するための秘訣のような気がします。

特に最近ブログを始めた者として非常に叱咤激励される内容でした。毎日20分のブログ執筆を目標にしようと思いました!

V$SQL_PLANでCRUD表モドキを作ってみる(番外編)

ピボットテーブルの縦横を入れ替える

テーブルの数とSQL文の数はたいていの場合後者の方が多いので、ピボットテーブルの横軸(列ラベル)にテーブル名(OBJECT_NAME)、縦軸(行ラベル)にSQL_IDを指定するのが自然だと思うのですが、列ラベルの高さは一番長いテーブル名の長さに合わせるため無駄に高くなってしまいます。

見やすさを追求するためには、下図のように列ラベルと行ラベルをドラッグ&ドロップで入れ替えてしまうというのも一つの工夫です。

PIVOT2

SQL_IDは13バイト全部を表示させる必要はありませんし、テーブル名が縦に並んでいる方が圧倒的に見やすいのではないでしょうか?

必要に応じてどちらを列ラベルに指定した方がよいかを考えて指定しましょう。

DBA_HIST_SQL*は上位◯個のSQL文しか格納しない

前回の投稿で本番業務への影響をできるだけ小さくするため、V$SQL_*の代わりにDBA_HIST_SQL_*を使った方がよいと書いたのですが、一つ注意点があります。

V$SQL_*の情報はすべてDBA_HIST_SQL*に保存されるのではなく、上位SQL(Top SQL)のみがフィルタリングされて格納されます。(これはDBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGSで指定する「TOPNSQL」で変更することが可能です。デフォルト「30」)

AWRはパフォーマンス問題を解決するための情報を取得するので、問題となりそうな一部のSQLの情報しか保持しません。従ってDBA_HIST_SQL_*によって得られる情報は網羅性に欠けるということを念頭に置いて下さい。