e-learning、オラクル研修、LMS(学習管理システム)のiStudy

e-learning、オラクル研修、LMS(学習管理システム)のiStudy

第35回 「パッケージ本体の初期化ブロック」

2012.09.24

秋の気配を感じるようになってきましたね。さあ今回もPL/SQLパッケージについて勉強しましょう。

第33回で、パッケージの構造は仕様部のみであるが、例外としてパッケージ本体の最後に実行部が記述可能であり、その話は別の機会にすると申し上げました。
今回はその話をしたいと思います。

パッケージ本体の実行部は、簡単にいうと、セッションごとにそのパッケージが初めて使われるときに、1度だけ自動実行される実行部です。
ですからユーザやアプリケーションが明示的に実行するものではなく、バックグラウンドで勝手に自動的に実行されます。
そして、同一セッションで1回のみ実行され、2度以上実行されることはありません。

ではその用途は何でしょうか?
結論から言えば、実行部ですから実行可能であればなにを実行しても構まいません。
しかし代表的な用途が一つあります。それは「パッケージ変数の初期化」です。
パッケージ本体の実行部のことを正式には「初期化ブロック」と言うのですが、それは代表的な用途がパッケージ変数の初期化であることに由来してます。

本メルマガの第30回で「パッケージ変数は、同じパッケージの同じ名前の変数でも、セッション毎に存在し、セッションが終わるまで値を保持する」と説明しました。
例えばPAC1パッケージのAという変数は、セッション1では、値10を格納していて、セッション2では値20を格納しているといったことですね。
パッケージ変数はそれぞれセッションごとに別物というわけです。

そこで、パッケージ変数が使われるときに、それぞれのセッション固有の値で自動的に初期化しておきたい場合、初期化ブロック(構文的にはパッケージ本体の実行部)を用意すればいいわけです。

では簡単な具体例をご紹介します。
セッション固有の値をパッケージの変数に格納する例ですが、なるべく簡単なものにするために、そのパッケージを初めてコールした時の日時をセッション固有の値にしようと思います。
とりあえず簡単に確認できるのでご覧ください。

<<1.パッケージ仕様部の作成>>
SQL> CREATE OR REPLACE PACKAGE PAC1
  2  IS
  3    -- 最初にこのパッケージをコールした日時
  4    --を格納する変数
  5    FirstCall   DATE;
  6  END;
  7  /

パッケージが作成されました。

【上記1の解説】
PAC1パッケージの仕様部を作くっています。
このパッケージをそのセッションで初めてコールした時点の日時(日付と時刻)を格納するつもりの変数をFirstCallという名前でDATE型で宣言してます。
なお、変数名の大文字小文字は区別しませんが、わかりやすくするために大文字小文字を使い分けました。
オラクルのDATE型は日付と日時の両方を含んでいます。

<<2.パッケージ本体の作成>>
SQL> CREATE OR REPLACE PACKAGE BODY PAC1
  2  IS
  3     -- IS の下は宣言部だがここでは宣言するものなし
  4     -- 以下のBEGINから実行部(初期化ブロック)
  5  BEGIN
  6     -- SYSDATE(現在の日時)を
  7     -- パッケージ仕様部のFirstCall変数に代入(初期化)
  8     FirstCall  := SYSDATE;
  9  END;
 10  /

パッケージ本体が作成されました。

【上記2の解説】
PAC1パッケージの本体を作っています。
ISからBEGINまでの間は構文的に宣言部ですが今回はここで宣言すべきものがなにもないのでなにも宣言していません。
BEGINの下が構文的に実行部ですが、この部分がセッションで1回だけ自動実行される初期化ブロックです。
ここではSYSDATE関数の戻り値をFirstCall変数に代入しているわけです。
これによりPAC1.FirstCall変数は必ずそのセッションで初めてこのパッケージを使った(コールした)時点の日時で初期化されます。

<<3.セッション1で>>
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
  2     DBMS_OUTPUT.PUT_LINE(TO_CHAR(PAC1.FirstCall,'yy/mm/dd hh24:mi:ss'));
  3  END;
  4  /
12/09/12 14:12:15   ←1回目 このとき初期化ブロックで初期化された

PL/SQLプロシージャが正常に完了しました。

SQL> R
  1  BEGIN
  2     DBMS_OUTPUT.PUT_LINE(TO_CHAR(PAC1.FirstCall,'yy/mm/dd hh24:mi:ss'));
  3* END;
12/09/12 14:12:15    ←2回目 初期化ブロック実行なし よって同じ値

PL/SQLプロシージャが正常に完了しました。

【上記3の解説】
セッション1で、PAC1.FirstCallの値を画面表示してます。
2回表示してますが、最初の1回目で初期化ブロックが起動し、その時点の日時がPAC1.FirstCallに格納されているわけです。
2回目では初期化ブロックが実行されていないので、PAC1.FirstCall変数の値は変わっていないことがわかります。

<<4.セッション2で>>
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
  2     DBMS_OUTPUT.PUT_LINE(TO_CHAR(PAC1.FirstCall,'yy/mm/dd hh24:mi:ss'));
  3  END;
  4  /
12/09/12 14:15:40   ←1回目 このとき初期化ブロックで初期化された

PL/SQLプロシージャが正常に完了しました。

SQL> R
  1  BEGIN
  2     DBMS_OUTPUT.PUT_LINE(TO_CHAR(PAC1.FirstCall,'yy/mm/dd hh24:mi:ss'));
  3* END;
12/09/12 14:15:40    ←2回目 初期化ブロック実行なし よって同じ値

PL/SQLプロシージャが正常に完了しました。

【上記4の解説】
こちらは別画面から接続してます。つまり別セッションです。
同じようにPAC1.FirstCall変数の値を参照してますが、セッション1とは違う値であることに注目してください。
また、連続2回表示してますが、1回目と2回目で値が同じであることに注目してください。
つまりこちらのセッションも、初期化ブロックは1回目のみ起動していることがわかります。

【まとめ】
いかがでしょうか?この例は単純ですが、パッケージ本体の初期化ブロックの働きをよくあらわしていると思います。
実際の応用例としては、例えば、セッションごとに各セッションが売り上げた売上の集計をする場面があるとします。
ボーナスの掛率がセッションごとに違う場合、そのボーナスの掛率をパッケージ化し、初期化ブロックでセッションを識別しセッションに応じた掛率で初期化すれば、共通のプログラムロジックでセッションに応じた売上の集計ができる、といったことができます。

いかがでしょうか?参考になりましたか?

しばらく続けてきたPL/SQLパッケージの話は、いったん今回までとします。
次回からデータベーストリガーについて説明していこうと思っています。
もしかしたら単発的にパッケージの話をするかもしれませんが、気楽に読んでいってください。

それでは今回はここまでです。次回ご期待ください。

先頭へ戻る