ブロックによる構築,
Steven Feuerstein著
PL/SQLの理解と利用に関するシリーズ記事のパート1
Oracle PL/SQLが誕生してから2011年で22年目を迎えました。なぜこのことを知っているのかと言うと、Oracle PL/SQLユーザー・ガイドの初刊を今調べているからです。このユーザー・ガイドはPL/SQL Release 1.0向けのもので、発行日は1989年9月になっています。私は当時オラクルに勤務し、Oracle USAの販売部隊が利用する初の販売自動化ツールを構築していました。私はそれ以前からSQL Forms 3.0のPL/SQLを使用していましたが、Oracle 6 Databaseのリリースにより、PL/SQLは独立したアプリケーション開発言語として利用できるようになりました。
その3年後、私はPL/SQLに関する初めての書籍を執筆しました。それ以降は仕事ではあまりPL/SQLに関わりませんでしたが、PL/SQLについて学習し、本当に多くのPL/SQLコードを記述して、この優れたデータベース・プログラミング言語について執筆しています。もちろん、私だけではなく、世界中の多くの開発者がそのリリース以来、Oracle PL/SQLベースのアプリケーションを数多く構築してきました。
特にすばらしいのは、新しいPL/SQL開発者が次々と着実に現れていることです。実際、比較的最近になってインド、中国、その他の技術大国が誕生したことで、まったく新しい世代の開発者がPL/SQLを探究し、習得しています。
新参のPL/SQL開発者がこの言語を最大限に活用できるように、Oracle Magazineから、PL/SQL初心者向けのシリーズを執筆しないかという話をいただきました。この記事は、そのパート1にあたります。経験豊富なPL/SQL開発者でも、PL/SQLの基本を手軽に思い出すためにこのシリーズをご利用いただけるでしょう。
このシリーズは、ある程度のプログラミング経験があり、SQLに慣れているPL/SQL初心者の方を対象としています。また、シリーズ全体を通じて、開発者ができる限りすぐにPL/SQLで生産的に開発できるようになるアプローチをとっていきます。
この質問に答えるにあたり覚えておくべき重要なことは、閲覧されるWebサイトや実行されるアプリケーションのすべてが、ソフトウェア・テクノロジーのスタックにより構成されているということです。スタックの最上層にはプレゼンテーション・レイヤー、つまりユーザーが直接操作する画面や対話型デバイスが位置します(プレゼンテーション・レイヤー実装用の言語の中で現在もっとも多く利用されているのは、Javaと.NETです)。スタックの最下層には、ハードウェアと通信するマシン・コードが位置します。
このテクノロジー・スタックの中間あたりに位置するものがデータベースです。データベースは、大量の複雑なデータを保存、操作するためのソフトウェアです。現在では、SQLを中心として確立されたリレーショナル・データベース・テクノロジーが有力なデータベース・テクノロジーとなっています。
SQLは、非常に強力な集合指向言語で、その唯一の目的はリレーショナル・データベースの内容を操作することです。Oracle Databaseを基盤としたアプリケーションを開発する開発者(またはテクノロジー・スタックのより低いレベルにあたるコードの開発者)は、そのデータベース内にあるデータの取得やデータの変更のためにSQL文を実行する必要があります。しかし、SQLを使用して、アプリケーションに必要となるすべてのビジネス・ロジックやエンドユーザー機能を実装することは不可能です。これが、PL/SQLを利用する動機になります。
PL/SQLは、Procedural Language/Structured Query Language(手続き型言語/構造型問合せ言語)の略です。PL/SQLでは一連の手続き型コマンド(IF文、ループ、代入)が提供されます。これらのコマンドをブロック内で構造化して、SQL機能の範囲を補完、拡張できます。
確かに、PL/SQLを使用しなくても、SQLやOracle Databaseの上層にアプリケーションを構築することは可能です。しかし、PL/SQLを利用してデータベース固有の操作(中でもSQL文)を実行することには複数の利点があります。この利点には、SQLとの緊密な統合、ネットワーク・トラフィックの削減によるパフォーマンスの向上、移植性などがあります(PL/SQLプログラムはあらゆるOracle Databaseインスタンスで実行できます)。そのため、多くのアプリケーションのフロントエンド・コードでは、SQL文とPL/SQLブロックの両方を実行して、パフォーマンスの最大化とアプリケーションの保守性の向上を同時に実現しています。
実行可能セクションのみが必須です。ブロック内で文を宣言する必要はなく、またそのブロック内で発生する例外を捕捉する必要はありません。
ブロック自体も実行可能文であるため、ブロックを別のブロック内にネストできます。
次にいくつかの例を挙げます。
BEGIN
DBMS_OUTPUT.put_line ('Hello World!');
END;
ファンクションとプロシージャ(名前付きブロックの種類)について詳しくは、この記事の後半で取り上げます。パッケージもこれらと同様ですが、簡単に説明すると、パッケージは、複数のファンクションおよびプロシージャを格納するコンテナです。Oracleでは多くの標準提供パッケージや組込みパッケージによってPL/SQLが拡張されます。
DECLARE
l_message
VARCHAR2 (100) := 'Hello World!';
BEGIN
DBMS_OUTPUT.put_line (l_message);
END;
ここで、変数にl_messageという名前を付けている点に注意してください。私は通常、ローカル変数(コードのブロック内で定義する変数)に対してl_という接頭辞を使用し、パッケージ内に定義するグローバル変数に対してはg_という接頭辞を使用します。
DECLARE
l_message
VARCHAR2 (100) := 'Hello World!';
BEGIN
DBMS_OUTPUT.put_line (l_message);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (SQLERRM);
END;
DECLARE
l_message
VARCHAR2 (100) := 'Hello';
BEGIN
DECLARE
l_message2 VARCHAR2 (100) :=
l_message || ' World!';
BEGIN
DBMS_OUTPUT.put_line (l_message2);
END;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line
(DBMS_UTILITY.format_error_stack);
END;
PL/SQLコードのブロックを記述したら、次にそのブロックを実行できます。PL/SQLコードを実行するためのさまざまなツールが存在します。もっとも基本的なツールはSQL*Plusです。SQL*Plusは、SQL文やPL/SQLブロックを実行するためのコマンドライン・インタフェースです。図1に、もっとも単純な"Hello World!"ブロック例をSQL*Plusで実行した例を示します。
SQL*Plusでデータベースに接続した後の最初の作業は、サーバー出力をオンにすることです。この操作によって、DBMS_OUTPUT.PUT_LINEをコールするとテキストが画面に表示されるようになります。次に、ブロックを構成するコードを入力します。最後に、スラッシュ(/)を入力して、SQL*Plusにこのブロックを実行させます。
この結果、SQL*Plusでブロックが実行され、画面に"Hello World!"と表示されます。SQL*Plusは、SQL文とPL/SQLブロックを実行するための最低限の機能を持つツールとしてオラクルが提供しています。SQL*Plusのみを使い続ける開発者もいますが、ほとんどの開発者は統合開発環境(IDE)を使用します。トレーニング・セッションで私が独自に行った非公式の調査によれば、そのようなIDEの中でも次のツールが特に人気があります。
PL/SQLブロックの作成、保存、実行、あるいはサーバー出力の有効/無効の切替えを行うためのウィンドウや手順は、それぞれのツールで少しずつ異なります。このシリーズでは、読者がSQL*Plusのみを利用すること、およびSQL*Plusコマンド・ウィンドウで記事のすべての文を実行することを想定します。
これまでに紹介したブロックはすべて"匿名"です。つまり、名前がありません。仮に、文を構造化するために匿名ブロックしか使用できないとすると、PL/SQLにより大規模で複雑なアプリケーションを構築することが非常に難しくなります。しかし、PL/SQLではコードの名前付きブロックの定義がサポートされます。名前付きブロックはサブプログラムとも呼ばれます。サブプログラムは、プロシージャにもファンクションにも適用できます。一般的に、プロシージャはアクションを実行するために使用し、ファンクションは計算して値を返すために使用します。サブプログラムについては、このシリーズの今後の記事で詳しく取り上げます。今のところは、サブプログラムの作成の基本的な考え方について理解しましょう。
ここで、アプリケーション内の複数の場所から"Hello World!"と表示する必要があると仮定します。これらすべての場所で同じロジックを繰り返し記述することは本当に避けるべきです。たとえば、このメッセージを"Hello Universe!"に変更する必要がある場合はどうなるでしょうか。このロジックが出現するコード内のすべての場所を検索する必要があります。
そうではなく、次のようなデータ定義言語(DDL)コマンドを実行して、hello_worldというプロシージャを作成することにします。
CREATE OR REPLACE PROCEDURE
hello_world
IS
l_message
VARCHAR2 (100) := 'Hello World!';
BEGIN
DBMS_OUTPUT.put_line (l_message);
END hello_world;
これで結果的にPL/SQLを拡張したことになります。データベースにインストールされているオラクル製のプログラム(DBMS_OUTPUT.PUT_LINEなど)のコールに加え、自作したサブプログラムもPL/SQLブロック内でコールできます。
BEGIN
hello_world;
END;
世界にどのようにあいさつするかの詳細についてはすべて、プロシージャの本体、すなわち実装内に隠しました。このhello_worldプロシージャをコールすれば、DBMS_OUTPUT .PUT_LINEへのコールを記述しなくても、あるいは文字列の適切な整形方法を検討しなくても、必要なメッセージを表示できます。このプロシージャは、アプリケーションのどの場所からでもコールできます。さらに、この文字列を変更する必要がある場合にも、この文字列の唯一の定義ポイントの1箇所を変更するだけで済みます。
hello_worldプロシージャは非常に単純です。実際に作成するプロシージャにははるかに多くのコードが含まれ、たいていはパラメータも伴います。パラメータは、サブプログラムがコールされたときにその内部に情報を渡すものです。パラメータを使用すれば、より柔軟で汎用的なサブプログラムを作成できます。パラメータは、さまざまなコンテキストで使用できます。
前述のとおり、"Hello World!"の代わりに"Hello Universe!"と表示したくなる日が来るかもしれません。そのような場合に、次のようにhello_worldプロシージャをコピーして、表示する文字列を変更することも可能ではあります。
CREATE OR REPLACE PROCEDURE
hello_universe
IS
l_message
VARCHAR2 (100) := 'Hello Universe!';
BEGIN
DBMS_OUTPUT.put_line (l_message);
END hello_universe;
しかし、この方法では、"同じ"helloプロシージャの大量のバリエーションを作成することになりかねません。そうなれば、アプリケーションの保守が非常に難しくなります。もっと良いアプローチがあります。それは、プロシージャを分析し、メッセージを変更する必要がある場合にも同一のままの部分(静的な部分)と、変更される部分を識別することです。変更される部分をパラメータとして渡せば、1つのプロシージャを異なる状況で使用できます。
したがって、hello_world(およびhello_universe)を、hello_placeという新しいプロシージャに変更します。
CREATE OR REPLACE PROCEDURE
hello_place (place_in IN VARCHAR2)
IS
l_message VARCHAR2 (100);
BEGIN
l_message := 'Hello ' || place_in;
DBMS_OUTPUT.put_line (l_message);
END hello_place;
プロシージャ名の直後に左括弧と右括弧を追加し、その括弧の中に1つのパラメータを指定しています。複数のパラメータを指定することもできますが、各パラメータは次のような同じ基本形に従います。
parameter_name parameter_mode datatype
つまり、パラメータの名前、パラメータの使用モードまたは使用方法(IN=読取り専用)、およびこのパラメータによりサブプログラムに渡すデータの型を指定する必要があります。
この例の場合は、hello_placeプロシージャに読取り専用の文字列を渡します。
これで、世界(world)と宇宙(universe)の両方に対してあいさつできるようになります。
BEGIN
hello_place ('World');
hello_place ('Universe');
END;
このシリーズの今後の記事で、再利用の考え方と繰り返しを避ける方法について詳しく確認しますが、この単純な例からでも、名前付きブロックの裏にロジックを隠す利点が分かるはずです。
次に、単に"Hello"メッセージを表示するだけでは満足できなくなったと仮定します。あるときには、データベース表にメッセージを保存する必要があり、またあるときには、文字列をWebブラウザに表示するためにホスト環境に返す必要があります。言いかえれば、"Hello"メッセージを構成する方法と、"Hello"メッセージを使用する(表示する、保存する、別のプログラムに送信するなど)方法を区別する必要があるのです。この必要な柔軟性レベルは、メッセージを構成するコードを専用のファンクション内に移動することで実現できます。
CREATE OR REPLACE FUNCTION
hello_message
(place_in IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN 'Hello ' || place_in;
END hello_message;
このサブプログラムと元のプロシージャとの違いは次のとおりです。
メッセージの構成に必要となるコードをhello_messageファンクション内に配置することで、このメッセージを複数の方法で使用できます。たとえば、このファンクションをコールしてメッセージを取得し、変数に代入できます。
DECLARE
l_message VARCHAR2 (100);
BEGIN
l_message := hello_message ('Universe');
END;
hello_messageファンクションをPL/SQL文の一部(この例では、文字列を変数に代入している部分)としてコールしている点に注意してください。hello_messageファンクションは文字列を返すため、任意の実行可能文で文字列の代わりに使用できます。
また、hello_placeプロシージャで、文字列の作成に使用するコードをこのファンクションのコールに置き換えることもできます。
CREATE OR REPLACE PROCEDURE
hello_place (place_in IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.put_line
(hello_message (place_in));
END hello_place;
さらに、このファンクションをSQL文の中からコールすることもできます。次のブロックでは、メッセージをデータベース表に挿入します。
BEGIN
INSERT INTO message_table (message_date, MESSAGE_TEXT)
VALUES (SYSDATE, hello_message('Chicago'));
END;
"hello place"ロジックは非常に単純ですが、1つ以上の実行可能文(アルゴリズム)に名前を割り当てて、その名前と必要なパラメータを指定するだけでそのアルゴリズムを参照できるという利点が分かるようになっています。
名前付きのPL/SQLブロックにより、比較的簡単に理解、保守可能な複雑なアプリケーションを構築できるようになります。
Oracle Databaseで有効な識別子を構成するためのルールは次のとおりです。
hello_world
hello$world
hello#world
しかし、次の文字列は無効です。
1hello_world
hello%world
hello_world
Hello_World
HELLO_WORLD
柔軟性を高めるために、Oracle Databaseでは、識別子を二重引用符で囲むことで、上記の2番目と3番目のルールによる制約を取り払うことができます。引用識別子には、二重引用符を除くすべての印刷可能文字列を含めることができ、大文字小文字の区別も維持されます。したがって、次の文字列はすべて有効で、異なる識別子になります。
"Abc" "ABC" "a b c"
PL/SQLコードでは引用識別子を使用することはほとんどありません。ただし、ネーミング規則に従うため、あるいは大文字小文字を織り交ぜた文字列の方が読みやすいという理由で引用識別子を使用する開発グループもあります。
同じルールがデータベース・オブジェクト(表、ビュー、プロシージャなど)の名前にも適用され、さらに次の追加ルールも適用されます。それは、データベース・オブジェクトの名前を二重引用符で囲まない限り、Oracle Databaseには名前が大文字で保存されるというルールです。
次のようなプロシージャを作成したとします。
CREATE OR REPLACE PROCEDURE
hello_world
IS
BEGIN
DBMS_OUTPUT.put_line
('Hello World!');
END hello_world;
このプロシージャはOracle DatabaseにはHELLO_WORLDという名前で保存されます。
次のブロックでは、このプロシージャを3回コールしています。すべてのコールで名前が異なるように見えますが、同じプロシージャが実行されます。
BEGIN
hello_world;
HELLO_WORLD;
"HELLO_WORLD";
END;
一方、Oracle Databaseでは、次のようにコールした場合にプロシージャを実行できません。
BEGIN
"hello_world";
END;
このコールにより、データベース内部では、HELLO_WORLDではなくhello_worldという名前のプロシージャが検索されます。
大文字で保存したくないサブプログラム名については、サブプログラムの作成時に名前を二重引用符で囲むようにしてください。
CREATE OR REPLACE PROCEDURE
"Hello_World"
IS
BEGIN
DBMS_OUTPUT.put_line
('Hello World!');
END "Hello_World";
PL/SQLはデータベース・プログラミング言語です。PL/SQLで記述するほぼすべてのプログラムで、SQLを使用してOracle Databaseの読取りまたは書込み、あるいはその両方が実行されます。このシリーズでは読者がSQLの実用的な知識を持っていると想定していますが、PL/SQLブロック内からSQL文をコールする方法を知っている必要があります。
うれしいことに、Oracle Databaseでは、PL/SQLでのSQL文の記述や実行が非常に簡単です。大部分で、PL/SQLブロック内でSQL文を直接記述し、そのSQL文とPL/SQLコードとの連結に必要となるコードを追加するだけで済みます。
たとえば、employeesという表があり、この表にemployee_idという主キー列とlast_nameという列があるとします。この場合、次のようにして、IDが138の従業員の姓を表示できます。
SELECT last_name
FROM employees
WHERE employee_id = 138
次に、この問合せをPL/SQLブロック内で実行して、姓を表示することにしましょう。そのためには、表から取得した姓をローカル変数に"コピー"する必要があります。この操作は、INTO句を使用して実行できます。
DECLARE
l_name employees.last_name%TYPE;
BEGIN
SELECT last_name
INTO l_name
FROM employees
WHERE employee_id = 138;
DBMS_OUTPUT.put_line (l_name);
END;
まず、ローカル変数を宣言していますが、そのために、PL/SQLのもう1つの優れた機能である、表の列に変数のデータ型を紐付ける機能(アンカー)を取り入れています(アンカーについて詳しくは、シリーズの今後の記事で取り上げます)。
次に、データベースに対して問合せを実行し、従業員の姓を取得してl_name変数に直接代入しています。
もちろん、PL/SQLで実行したいことはSELECT文だけではありません。PL/SQLでは、表への挿入、表の更新、表からの行の削除も実行したいことでしょう。それぞれの種類のデータ操作言語(DML)文について、次に例を示します。
DECLARE
l_dept_id
employees.department_id%TYPE := 10;
BEGIN
DELETE FROM employees
WHERE department_id = l_dept_id;
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
END;
この例では、DELETE文の内部でPL/SQL変数を直接参照しています。このブロックを実行すると、変数名が実際の値の10に置き換えられた後に、SQLエンジンによってDELETEが実行されます。SQL%ROWCOUNTは、セッション内で最後に実行されたDML文によって変更された行数を返す特別なカーソル属性です。
DECLARE
l_dept_id
employees.department_id%TYPE := 10;
BEGIN
UPDATE employees
SET salary = salary * 1.2
WHERE department_id = l_dept_id;
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
END;
表に新しい従業員を挿入します。
BEGIN
INSERT INTO employees (employee_id
, last_name
, department_id
, salary)
VALUES (100
, 'Feuerstein'
, 10
, 200000);
DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
END;
このブロックでは、すべての列の値を変数ではなくリテラルとして、SQL文の内部で直接指定しています。
PL/SQLを始めたばかりの開発者は、無料のオンライン・リソースが大量にあることを認識して、ぜひ利用してください。使いやすく人気のあるリソースの一部を次に紹介します。
この記事では、より広範なOracle Databaseの世界に対するPL/SQLの位置付けについて説明しました。また、PL/SQL文を実行するコード・ブロックの定義方法や、アプリケーション・コードでより簡単に利用、維持できるようにするためのブロックの名前の付け方についても説明しました。最後に、PL/SQL内でのSQL文の実行方法について紹介しました。
その他の記事 PL/SQLのコラム
ディスカッション PL/SQL
ダウンロード Oracle Database 11g
テスト PL/SQLの知識
1つ以上の実行可能文で構成される任意のセットの前にBEGINを配置し、そのセットの後にENDを配置することで、それらの実行可能文をネストするブロックを作成できます。ブロックのネストにはおもに2つの利点があります。(1)ネストされたブロック内でのみ必要となる変数について、メモリの割当てを遅らせることができます。(2)ネストされたブロック内のいずれかの文によって発生する例外が伝播する範囲を限定できます。
次のブロックについて考えてみます。
DECLARE
l_message VARCHAR2 (100) := 'Hello';
l_message2 VARCHAR2 (100) := ' World!';
BEGIN
IF SYSDATE >= TO_DATE ('01-JAN-2011')
THEN
l_message2 := l_message || l_message2;
DBMS_OUTPUT.put_line (l_message2);
ELSE
DBMS_OUTPUT.put_line (l_message);
END IF;
END;
このコードにより、SYSDATEによって返される今日の日付が2011年の元旦以降であれば"Hello World!"と表示され、2011年の元旦より前であれば"Hello"メッセージのみが表示されます。しかし、このブロックを2010年に実行した場合でも、l_message2変数用のメモリが割り当てられます。
このブロックを次のように再構成した場合、l_message2用のメモリは2010年を過ぎた後でのみ割り当てられるようになります。
DECLARE
l_message VARCHAR2 (100) := 'Hello';
BEGIN
IF SYSDATE > TO_DATE ('01-JAN-2011')
THEN
DECLARE
l_message2 VARCHAR2 (100) := ' World!';
BEGIN
l_message2 := l_message || l_message2;
DBMS_OUTPUT.put_line (l_message2);
END;
ELSE
DBMS_OUTPUT.put_line (l_message);
END IF;
END;
同様に、このネストされたブロックに、エラーを捕捉して外側のブロックでの実行を継続するように例外セクションを追加できます。
DECLARE
l_message VARCHAR2 (100) := 'Hello';
BEGIN
DECLARE
l_message2 VARCHAR2 (5);
BEGIN
l_message2 := 'World!';
DBMS_OUTPUT.put_line (
l_message || l_message2);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (
DBMS_UTILITY.format_error_stack);
END;
DBMS_OUTPUT.put_line (l_message);
END;
この場合、ネストされたブロックではVALUE_ERROR例外が発生します。l_message2が最大5バイトであり、"World!"文字列に対して小さすぎるためです。ネストされたブロックの例外セクションにより、エラーが捕捉されて表示されます。その後、この外側のブロックで実行が継続します。
シリーズの今後の記事では、PL/SQLでの例外処理のしくみについて詳しく説明します。
シリーズの次回の記事では、ブロック内での実行フローの制御方法、IFおよびCASEを使用した条件ロジック、FOR、WHILE、単純なループを使用した反復ロジック、例外の発生と処理について説明します。
Steven Feuerstein(steven.feuerstein@quest.com)は、Quest SoftwareのPL/SQLエヴァンジェリストです。これまで、Oracle PL/SQL ProgrammingやOracle PL/SQL Best Practices(O'Reilly Media)などを含む、Oracleのプログラミング言語に関する著書を10冊発行しています。