投稿者「三原健一」のアーカイブ

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_*によって得られる情報は網羅性に欠けるということを念頭に置いて下さい。

V$SQL_PLANでCRUD表モドキを作ってみる③

ここからはExcelで

前回のクエリーの結果をMicrosoft Excelのピボットテーブルを利用して集計してみましょう。(Excel2010での方法を説明します。)
今回はある架空アプリケーションにおける実績データを対象とします。

一番簡単なのはSQL*Plusの実行結果を以下のようにExcelのシートにコピー&ペーストするやり方です。

Excelにコピー&ペースト

 データをスペースで区切る

データ>区切り位置 で「区切り位置指定ウィザード」を呼び出し、「スペースによって右または左に揃えられた固定長フィールドのデータ」を選択して、データを区切ります。

区切り位置指定ウィザード

ピボットテーブルで集計

データを区切ったら、A〜C列(CRUD, SQL_ID, OBJECT_NAME)を選択した状態から、挿入>ピボットテーブル で「ピボットテーブルの作成(ウィザード)」を呼び出します。配置する場所が「新規ワークシート」となっていることを確認し、「OK」をクリックします。

ピボットテーブルウィザード

右側にピボットテーブルのフィールドリストを指定する画面が出てくるので、リストのそれぞれを下のボックスにドラッグして指定します。

  • レポートフィルタ: CRUD
  • 列ラベル: OBJECT_NAME
  • 行ラベル: SQL_ID
  • 値: CRUD(データの個数となっていることを確認)

ピボットテーブル設定

これで完成ですが、見やすくするためにテーブル名を90°回転させて列幅を揃える等の整形をします。

CRUD表を使ってみる

作成したCRUD表(モドキですが)は縦にSQL(SQL_ID)、横にテーブル名が並んだ表で、それぞれの交差部分に「1」が立っていれば、そのSQL文で該当するテーブルを使っていることを意味します。

まず、下図のB1カラム(レポートフィルタ)で「R」を選択し、SELECT文を見てみましょう。

1つのSELECT文で参照するテーブル数

例えば、SQL_ID = 2p8c09m8rupuk のSQL文は「ABCD_BHA」「NIC」「STU3」という3つのテーブルをアクセスしています。一番右端の総計欄を見るとSQL文でアクセスしているテーブル数の合計がわかります。(表を横に見ます。)

R1

この合計数が多いSQLはジョインしているテーブルの数が多い可能性が高いため、注目する必要があるものです。

あるテーブルを使用しているSQL文とその数

逆にあるテーブルから関連するSQL文とその数を確認するには、表を縦に見ます。

R2

上図の例では、「EFGH_DAY_OUT」というテーブルは一番下の総計欄が「34」になっています。これは34種類の異なるSQL文で使用されていることを示しています。

この数が多いということは

  1. 多くのSQL文でアクセスされるホットなテーブルである。
  2. バインド変数を使わないために類似SQL文が多数存在している。

という2つの可能性を示しています。

いずれにせよ、パフォーマンス・チューニングで注目すべきポイントになりますので、AWRレポート等他の情報と突き合わせて問題の特定を行うようにしましょう。

また、このあるテーブルに関係するSQL文の一覧を得ることは、テーブル定義の変更で影響を与える対象を把握するのにも役立ちます。

SQLテキストと実行計画を確認する

一覧ではSQL文を表示させていません。SQL_IDが特定できたら以下のSQL文を実行してSQLテキストと実行計画を確認します。

set pages 1000
set lines 140
select plan_table_output from table(DBMS_XPLAN.DISPLAY_CURSOR('<SQL_ID>'));

 

応用編

インデックス – SQL

V$SQL_PLANを使えば、テーブルとSQLの関係だけでなく、インデックスとSQLの関係を分析することもできます。

例えば、あるインデックスの定義を変更しようとする場合、1つのSQLだけに注目してしまうと他のSQLに影響があることに気づかず新たな問題を引き起こしてしまうかもしれません。

そのような場合、インデックスとSQLの相関表が役に立ちます。

DBA_HIST_*を使う

V$SQL_PLANもX$SQLAREAもメモリ(SGA)上にキャッシュされている情報です。従って、共有プールの使用率が高い場合等にはどんどんキャッシュから追い出されてしまい欲しい情報が得られない場合があります。

もし、AWRが使える環境であれば

  • V$SQL_PLAN → DBA_HIST_SQL_PLAN
  • V$SQLAREA → DBA_HIST_SQLTEXT

に置き換えれば、AWR(SQL)レポート保存期間以内のデータが保証されますので、事後解析でも有用なデータが得られます。

念のためAWR版の情報取得SQLを以下に紹介します。

select
 decode(CRUD_TYPE_ID,2,'C'
                    ,3,'R'
                    ,6,'U'
                    ,7,'D') CRUD
,SQL_ID
,OBJECT_NAME
,OPTIONS
from
(
select distinct
 SQL_ID
,OBJECT_NAME
,decode(operation,'TABLE ACCESS',3
                 ,'UPDATE',6
                 ,'DELETE',7) CRUD_TYPE_ID
,OPTIONS
from
 DBA_HIST_SQL_PLAN --V$SQL_PLAN
where OPERATION in ('TABLE ACCESS','UPDATE','DELETE')
and   OBJECT_OWNER ='xxxx'
union all
select
 SQL_ID
,ltrim(
  replace(
   upper(
    substr(
     regexp_substr(DBMS_LOB.SUBSTR(SQL_TEXT, 1000, 1),'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
        ,6)
         )
    ,'"')
      ) OBJECT_NAME
,COMMAND_TYPE CRUD_TYPE_ID
,null OPTIONS
from
 DBA_HIST_SQLTEXT --V$SQLAREA
where COMMAND_TYPE = 2
) I
,DBA_TABLES D
where I.OBJECT_NAME = D.TABLE_NAME
and   D.OWNER       = 'xxxx'
order by
 CRUD_TYPE_ID
,SQL_ID
,OBJECT_NAME
;

最後に注意事項

V$SQL_PLAN等は負荷の高い状況ではアクセスしないことをお勧めします。DBAが手動で負荷状況を見ながら注意深く実行して下さい。

従って、スクリプトを組んで定期的に自動実行するような運用は行わない方が望ましいと思います。

すきやばし次郎は高いか?

20分3万円

今日はちょっと技術者の値段について考えたいと思います。

銀座に「すきやばし次郎」というミシュランの三ツ星を何年も獲得している伝説的な寿司屋があるそうです。
オバマ大統領の来日で一躍有名になりましたが、私は当然行ったことはありません。だから全部思い入れだけで書きます。

何でも、おまかせコースが3万円からだそうです。(この「から」というのがとても気になりますが。。。)
にぎり寿司が次から次へと無言で出てきて大体20分で(早い人は15分!!)食べ終わってしまい、お会計は3万円也です。驚きです!

ネットで検索したらこんな記事も見つかりました。
すきやばし次郎が20分3万円の寿司でも客が途絶えない本当の理由

文章を見た限りでは、この記事を書いた方は多分実際には食べてないでしょうし、実際にどんなお客さんが常連なのか調べたんでしょうか?

それにしても20分で3万円!時給換算で9万円!!その辺の回転寿司の店員の時給が1000円としても90倍です!!!

映画と本

2011年にはアメリカ人の監督が作った「二郎は鮨の夢を見る」という映画が公開され、私も最近WOWOWで観ました。

映画の中で私が最も印象に残った言葉は、店主の小野二郎氏が語った「お客さんの前に立った時には9割5分の仕事は終わっている。」というものです。
つまり、店主以外の店員が築地市場への仕入からネタの仕込みまで丹念な仕事をして、最後の5%の仕事は店主が握る熟練の技ということです。

それから「すきやばし次郎 旬を握る」という本を私も買って読みました。小野次郎氏は1925年生まれの88歳。寿司職人になって63年目だそうです。

この本には小野二郎氏が今まで培ってきた技術・経験のうち、文字や写真で語ることができる恐らく全体から比べるとホンの僅かな知識と技が満載です。

例えば、「第2章 本マグロを握る」ではP.97-158まで、本マグロの月毎の断面カラー写真やマグロに関する熱い想いなどがぎっしり詰まっています。何かに人生をかけるということはこういうことなのかと純粋に感動しました。

何度も言いますが私は「すきやばし次郎」に行ったことはありませんし、上の紹介記事のように私のような庶民には確かに行ってはいけない店なのかもしれません。

でも、映画と本を観て読んで、20分3万円の理由がちょっとだけわかったような気がしました。

いつ来るか分からない15分のための準備

我々エンジニアの仕事は一言で「知識集約職業」とでも言えるでしょうか?もちろん他の職業でも同じようなことは言えますが、この仕事は特にそうなのではないかと思います。

以前、ある企業から新人研修の講師として「Oracle入門」を6回シリーズくらいで教育を行うという仕事を請け負ったことがあります。大学を卒業したばかりで右も左もわからないというような集団でしたが、中に数人キラっと光る人達が居ました。

わからないことは積極的に質問してくるし、教えていないこともどんどん本を読んで吸収していく姿は本当に輝いていました。

最近その企業を5〜6年ぶりに訪問する機会があり、そんな教え子の一人と席を並べて仕事をする機会がありました。
今では技術的な打ち合わせもリーダーとして立派に取り仕切っているし、本当に生き生きと仕事をしていることを見て非常に嬉しく思いました。

私が教えた「Oracle入門」は現在の彼の業務とは直接関係ないのですが、机の上にはOracleの参考書が並んでいたり、私が少し専門的なことを話してもびっくりするくらいついてきてくれます。

逆に、かなり経験を積んだようなエンジニアでも時々がっかりする人がいます。知識はいろいろ豊富みたいなのですが、役立つ情報がサッと出て来ない。知識が整理されていなくて何を言っているのかさっぱりわからない。つまり本当に使える知識となっていないのです。

障害が起きた時に本当の姿が見える

IT運用はうまく行って当たり前の世界なので、極稀に起きるトラブル時にエンジニアの真価が発揮されるのではないかと思います。

トラブルシュートのためのコマンドが構文エラーのために実行できない!後ろでは「まだ復旧できないのか!」という無責任はギャラリー達。

こんな時に冷静に問題を分析して、何が間違っているのか知識をフル動員して見極める。こんなエンジニアが真のプロフェッショナルです。

自分の記憶だけに頼って間違ったコマンドを叩き続ける。なぜ冷静にマニュアルを見返せないのでしょう。それから安易にGoogle検索に頼るというのも考えものです。最初からGoogleに頼ると間違った情報に行き着くかもしれません。

Google検索を全面的に否定するわけではありません。最後の手段として活用するのはよいことだと思います。世界中の知識が集まっているのですから。

自分の知識データベースを育てよう

話変わって、「プロとは?」というテーマで最近「いつ来るか分からない15分のために常に準備をしているのがプロ、デザイナー奥山清行による「ムーンショット」デザイン幸福論」という記事を読みました。(これは「運用エンジニアから開発エンジニアになるためにやったこと」というなかなか秀逸な記事からたどっていったものです。)

いつ来るかわからない障害にあるいはいつ来るかわからない支援要請に備えて、プロフェッショナル・エンジニアとしての我々は何をすべきか?

  • 検証環境でとにかく手を動かして、コードを書く。コマンドを叩く。結果を確認する。
  • 文献を読んで重要な部分のコピーを手元に置いておく。
  • 経験・知識を体系的にまとめる

私はこのような用途には、Evernoteは最適な道具だと思います。

私のEvernoteには様々な知識情報が集積されています。「技術情報」というノートブックには1300件以上の「いつ来るかわからない」ことに対して役立つかもしれない情報(ノート)が蓄積されていて、日々増殖しています。

ノートはタグ付けされていて、探したいキーワードを入れると関連するノートが瞬時にピックアップされます。MacBookでまとめた情報はiPadでもiPhoneでもネットワークが繋がっていればどこでも見ることができますし、オフラインノートブックにしておけば電波が届かない場所でも参照することが可能です。

「自分だけのGoogle」これが理想です。

Evernoteを知らないエンジニアはとても不幸だと思います。

知っていても使うことができないエンジニアはもっと不幸です。私が知っているある大企業ではEvernoteのようなクラウドサービスはセキュリティ上の理由でご法度だそうです。

このような会社に限って社内サーバルームに設置してあるファイルサーバが使えなくなって大騒ぎになったりするんですね。

話がだいぶ逸れましたが、エンジニアのためのEvernote活用術は別途書きたいと思います。

結局、すきやばし次郎は高いか?

私がこの業界に入ってまだ日が浅かった頃、あるアプリケーション開発者が1日かけても解決できないことを5分足らずで解決してしまうスーパー・エンジニアが居ました。「ああなれたらいいな」と思って今日まできたと思います。

1日8時間として5分は約90分の1。次郎と回転寿司といい勝負です。でも、結局解決できないのであれば倍率は無限大です。

アマチュアに対するプロのエンジニア、生産性で比べたらミシュラン三つ星のすきやばし次郎にも匹敵する価値があるかもしれません。

価値を感じてもらえる。そんなエンジニアになりたいものです。

でも、一度は次郎に行ってみたい!

 

V$SQL_PLANでCRUD表モドキを作ってみる②

UPDATEとDELETE

UPDATE文とDELETE文の実行計画については以下の例のとおりです。

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | UPDATE STATEMENT   |        |     1 |     8 |     1   (0)| 00:00:01 |
|   1 |  UPDATE            | EMP    |       |       |            |          |
|*  2 |   INDEX UNIQUE SCAN| PK_EMP |     1 |     8 |     0   (0)| 00:00:01 |
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | DELETE STATEMENT   |        |     1 |    26 |     1   (0)| 00:00:01 |
|   1 |  DELETE            | EMP    |       |       |            |          |
|*  2 |   INDEX UNIQUE SCAN| PK_EMP |     1 |    26 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Operation=UPDATE or DELETE となる Name列が示すものが、対象のテーブル名です。

SELECTと同様にV$SQL_PLANから情報を取得するクエリーは以下のとおりです。

select distinct
 SQL_ID
,OBJECT_NAME
,decode(operation,'UPDATE',6
                 ,'DELETE',7) CRUD_TYPE_ID
from V$SQL_PLAN
where OPERATION in ('UPDATE','DELETE')
and   OBJECT_OWNER = 'xxxx'
order by
 OBJECT_NAME
,CRUD_TYPE_ID
,SQL_ID
;

SELECTも一緒に取得するように書きなおしたSQL文の実行例は以下のとおりです。

SQL> select distinct
   2   SQL_ID
   3  ,OBJECT_NAME
   4  ,decode(operation,'TABLE ACCESS',3
   5                   ,'UPDATE',6
   6                   ,'DELETE',7) CRUD_TYPE_ID
   7  ,OPTIONS
   8  from
   9   V$SQL_PLAN
  10  where OPERATION in ('TABLE ACCESS','UPDATE','DELETE')
  11  and   OBJECT_OWNER = 'SCOTT'
  12  order by
  13   OBJECT_NAME
  14  ,CRUD_TYPE_ID
  15  ,SQL_ID;

 SQL_ID        OBJECT_NAME  CRUD_TYPE_ID OPTIONS
 ------------- ------------ ------------ --------
 a2dk8bdn0ujx7 EMP                     3 FULL
 2r07krus3ums3 EMP                     6
 7hr1wxxb8hkqc EMP                     7 

INSERTは難しい

INSERT文も同じような要領で情報を取れると思ったのですが、なかなかやっかいです。

以下はINSERT文の実行計画です。

---------------------------------------------------------------------------------
| Id  | Operation                | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |      |     1 |    38 |     1   (0)| 00:00:01 |
|   1 |  LOAD TABLE CONVENTIONAL | EMP  |       |       |            |          |
---------------------------------------------------------------------------------

Operation=”LOAD TABLE CONVENTIONAL” となるName列を持ってくればよさそうな気がします。
しかし、いろいろなINSERT文を見ていて次のような実行計画に出くわしました。

--------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name                      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                           |       |       |       | 47068 (100)|          |
|   1 |  LOAD TABLE CONVENTIONAL |                           |       |       |       |            |          |
|*  2 |   HASH JOIN              |                           | 22794 |    44M|    44M| 47068   (1)| 00:09:25 |
|*  3 |    TABLE ACCESS FULL     | Txxxxxxxxxxxxxxxxxxxxxxxx | 22794 |    44M|       |  1922   (1)| 00:00:24 |
|   4 |    TABLE ACCESS FULL     | Mxxxxxxxxxxx              |  4861K|   148M|       | 32769   (1)| 00:06:34 |
--------------------------------------------------------------------------------------------------------------

これは実際の実行計画のテーブル名をマスクしたものですが、INSERT対象のテーブル名はどこにも出てきていないことがおわかりでしょうか?

つまり、実行計画からINSERT先のテーブルを特定することはできないのです。

正規表現を使う

そこでV$SQLAREAを使って情報を取得することにしました。SQL_TEXT列にSQLテキストの先頭1,000バイトが入っているので、そこから文字列操作でテーブル名を取り出す作戦です。

SUBSTR関数等いろいろ試してみたのですが、最終的に10g以降で使えるようになった正規表現機能で抜き出すことにしました。
以下が情報取得用のクエリーです。

select
 SQL_ID
,ltrim(
  replace(
   upper(
    substr(
     regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
        ,6)
         )
    ,'"')
      ) OBJECT_NAME
,COMMAND_TYPE CRUD_TYPE_ID
,null OPTIONS
from V$SQLAREA
where COMMAND_TYPE = 2
order by
 SQL_ID
,OBJECT_NAME
;

解説:

  • 7行目がもっとも肝になる部分です。「INTO [テーブル名]」となる文字列をregexp_substr関数で抜き出します。
  • substr関数で抜き出した文字列の6バイト目以降をテーブル名として取り出します。(6行目)
  • upper関数でテーブル名を大文字に統一します。(5行目)
  • たまにテーブル名がダブルクォートで囲まれている場合があるのでreplace関数で取り除きます。これが最終的なOBJECT_NAMEとなります(4行目)

以下は実行例です。
V$SQLAREAにはテーブルの所有者を特定できる情報がないので、上記クエリーの結果とDBA_TABLESをジョインしてOWNER列で絞り込む条件を加えています。

SQL> select distinct
  2   SQL_ID
  3  ,OBJECT_NAME
  4  ,CRUD_TYPE_ID
  5  ,OPTIONS
  6  from
  7  (
  8  select
  9   SQL_ID
 10  ,ltrim(
 11    replace(
 12     upper(
 13      substr(
 14       regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
 15          ,6)
 16           )
 17      ,'"')
 18        ) OBJECT_NAME
 19  ,COMMAND_TYPE CRUD_TYPE_ID
 20  ,null OPTIONS
 21  from
 22   V$SQLAREA
 23  where COMMAND_TYPE = 2
 24  ) I
 25  ,DBA_TABLES D
 26  where I.OBJECT_NAME = D.TABLE_NAME
 27  and   D.OWNER       = 'SCOTT'
 28  order by
 29   SQL_ID
 30  ,OBJECT_NAME
 31  ;

SQL_ID        OBJECT_NAME                    CRUD_TYPE_ID OPTIONS
------------- ------------------------------ ------------ --------
7071f28x901k7 EMP                                       2
gjvsdk24ufadk EMP                                       2

UNIONでまとめてみる

最後に、INSERT/SELECT/UPDATE/DELETE の結果をまとめてみましょう。24行目の「union all」に注目して下さい。

補足すると、CRUD_TYPE_IDをdecode関数で「C」「R」「U」「D」に変換しています。(4行目)
なぜ、このような面倒くさいことをやっているのかというと、最初から「C」「R」「U」「D」という文字列にすると、この順番にソートしてくれないのでCRUD_TYPE_IDという数値を使っているのです。(細かいですが私のこだわりです。)

それから、SQL*Plusコマンドを使って、CRUD別のSQL数も集計してみました。(1-2行目)

SQL> break on crud skip page
SQL> compute count of sql_id on crud
SQL> select
  2   decode(CRUD_TYPE_ID,2,'C'
  3                      ,3,'R'
  4                      ,6,'U'
  5                      ,7,'D') CRUD
  6  ,OBJECT_NAME
  7  ,SQL_ID
  8  ,OPTIONS
  9  from
 10  (
 11  select distinct
 12   SQL_ID
 13  ,OBJECT_NAME
 14  ,decode(operation,'TABLE ACCESS',3
 15                   ,'UPDATE',6
 16                   ,'DELETE',7) CRUD_TYPE_ID
 17  ,OPTIONS
 18  from
 19   V$SQL_PLAN
 20  where OPERATION in ('TABLE ACCESS','UPDATE','DELETE')
 21  and   OBJECT_OWNER = 'SCOTT'
 22  union all
 23  select
 24   SQL_ID
 25  ,ltrim(
 26    replace(
 27     upper(
 28      substr(
 29       regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
 30          ,6)
 31           )
 32      ,'"')
 33        ) OBJECT_NAME
 34  ,COMMAND_TYPE CRUD_TYPE_ID
 35  ,null OPTIONS
 36  from
 37   V$SQLAREA
 38  where COMMAND_TYPE = 2
 39  ) I
 40  ,DBA_TABLES D
 41  where I.OBJECT_NAME = D.TABLE_NAME
 42  and   D.OWNER       = 'SCOTT'
 43  order by
 44   CRUD_TYPE_ID
 45  ,OBJECT_NAME
 46  ,SQL_ID
 47  ;

C OBJECT_NAME          SQL_ID        OPTIONS
- -------------------- ------------- ---------------
C EMP                  7071f28x901k7
  EMP                  gjvsdk24ufadk
*                      -------------
c                                  2

C OBJECT_NAME          SQL_ID        OPTIONS
- -------------------- ------------- ---------------
R EMP                  7071f28x901k7 BY INDEX ROWID
  EMP                  a2dk8bdn0ujx7 FULL
  EMP                  b84685tx0a4u0 BY INDEX ROWID
  EMP                  f93ucvwf6d1vh FULL
*                      -------------
c                                  4

C OBJECT_NAME          SQL_ID        OPTIONS
- -------------------- ------------- ---------------
U EMP                  2r07krus3ums3
*                      -------------
c                                  1

C OBJECT_NAME          SQL_ID        OPTIONS
- -------------------- ------------- ---------------
D EMP                  7hr1wxxb8hkqc
*                      -------------
c                                  1

どのテーブル(OBJECT_NAME)がどのSQL(SQL_ID)によってINSERT/SELECT/UPDATE/DELETEの対象になっているかが、この一覧からわかります。
(OPTIONSは単なるおまけですのでなくても構いません)

この例ではSCOTT.EMP表しか出てきていませんが、実際は非常に沢山の結果が返ってくるはずです。

次回は、MS Excelのピボットテーブルを使って CRUD表らしく加工してみます。

(続く)