PL/SQL

PL/SQLは、Oracle社が、Oracle Databaseのためにコンピュータのデータベース言語SQLを独自に拡張したプログラミング言語で、Ada言語を参考にして開発された言語である。PL/SQLは1988年にOracle Databaseに対して初めて追加された。

もともと非手続き型言語であるSQLを手続き型言語  に拡張するところから「PL/SQL」と命名された経緯を持つ。PL/SQLを使用すると、手続き型言語で埋め込みSQLによる処理と同様に、変数の利用やif、for、loopなどの制御構造を記述してビジネスロジックをデータベース内に実装することができ、問い合わせ結果の行を一件ずつ処理する場合には、カーソルによるFETCHループでの処理を行うことが出来る。数値計算の効率化のため、PL/SQL独自のデータ型も追加されている。BLとSQLを別々の言語で記述するよりも高効率に処理できるが、可読性の問題からBLの全てをPL/SQLで代替することは難しい。

1. 他のデータベースの言語との違い
------------------

同じくデータベース言語 SQL の拡張として、Sybase ASE や Microsoft SQL Serverで実装されているTransact-SQL と比較されることがあるが、文法・構文の違いだけでなく、命令のSQLエンジンでの処理のされ方が異なる。Transact-SQLは全ての構文をSQLエンジンだけで処理するが、PL/SQLの場合は、PL/SQL構文はPL/SQLエンジンでの解析・実行が行われ、データ操作言語  構文は従来のSQLエンジンにて解析・実行するように内部分岐している。このため、Transact-SQLでのSELECT結果は、ストアドプロシージャから結果セットを直接呼び元へ返却することが可能であるが、PL/SQLではそれができないため、パッケージ定義のカーソルを経由した引渡しや配列変数をつかったデータの返却などで回避しなければならない。

また、PostgreSQLのPL/pgSQLは、PL/SQLを参考に実装されたものであるが、データベース管理システム  の実装に依存する部分や、出力引数の扱いが異なるなど完全な互換性はない。

IBMのDB2はOracleからの乗り換えを狙ってV9.7からPL/SQLをサポートしている。またMariaDBも10.3からOracle Database互換機能としてPL/SQLをサポートしている。

2. 文法
-----

2.1. Ada言語との関係
--------------

PL/SQLの文法・構文はAda言語をベースにしている。 Oracle Corporation CEOのラリー・エリソンが、Oracle を開発する前に国防総省向けのシステム開発をAda言語によって行っていた。そのシステム計画は失敗したが、その経緯からAda言語を元にすることを採用したと推測される。ちなみにそのシステムの計画名は "Oracle" と呼ばれていた。

2.2. データ型
---------

データ型については、変数は文字列型などの基本的な型以外に、ネストした表、レコード型、レコード型の配列、索引付き表が可能。それ以外にPL/SQLでは、「XXテーブルのYY列の型」や「XXテーブルのレコードの型」といった間接的な型指定が可能なため、テーブルなど依存するデータベースオブジェクトの変更に対しても再解析だけで対応でき、また、リコンパイルで問題が発生しない場合にかぎり、実行時にPL/SQLエンジンが自動的にリコンパイルを試みるという利点がある。

2.3. ブロックと例外
------------

PL/SQLは基本的に、BEGIN ～ END、もしくは DECLARE ～ BEGIN ～ END による「ブロック」の中にコードを記述する。 PL/SQLブロック内で発生する例外・エラーをキャッチする場合には、BEGINとENDの間にさらにEXCEPTION句を差込み、そこからEND句 までの間に例外処理を記述する。このブロックは、入れ子記述をすることができるため、 Java言語の try ～ catch のように利用できる。

変数、定数、カーソルなどの定義は DECLARE ～ BEGINに記述する。

このブロックを直接記述したものを、「無名ブロック」と呼び、即時に実行される。一方、ストアドプロシージャ・ストアドファンクション・データベーストリガ・パッケージはすぐには実行されず、サーバ内に評価済みの形で登録される。

2.4. コメント
---------

コメントは、行の先頭に '--' と記述してから改行まで、もしくはC言語のように /* ～ */で囲んだ箇所に記述する。

3. ストアドプロシージャ・ストアドファンクション
-------------------------

ストアドプロシージャ・ストアドファンクションは、PL/SQLのコードに名前を付けて、解析したコードをサーバに登録し、呼び出しが可能な状態にすることができる機能。プロシージャは戻り値無し、ファンクションは戻り値があるという違いだけである。 通常のSQL文と比較して、ストアドファンクション・プロシージャの方が、実行時に解析済みのため処理が早いが、参照しているデータベーススキーマを変更すると、再解析が必要になる。

ストアドファンクションは通常のSQL文の関数として使用することができる。

4. データベーストリガ
------------

登録されている表に対して、行追加、更新、削除の前後のタイミングで自動的に処理を走らせるPL/SQLブロックを登録することができる。これをデータベーストリガと呼ぶ。処理されるデータ1件ずつ処理することができ、また変更前と変更後の値を使うことができる。起動条件を細かく設定することも可能である。本来は「制約」定義で管理できない参照整合性を管理するために用いるものである。業務処理を暗黙に組み込みすぎると、業務処理の変更があった場合に他の応用プログラムの挙動に影響を与えやすいため、注意して利用すべきである。

エラー処理など考慮すべき点は多いが、非正規化した表の同期に使用すると便利である。

5. パッケージ
--------

PL/SQLがAda言語の特徴を色濃く示すところは「パッケージ」の概念を取り入れているところである。 これは、C言語による静的変数やヘッダファイルによるアクセスコントロールの機能に似たものであり、永続性のある変数や、他のパッケージからの参照を許さない変数やストアドプロシージャ等を作ることができる。

しかし、インスタンス化ができないため、オブジェクト指向言語におけるクラスとは本質的に異なる。 ただ、そのパッケージが初めて呼ばれたときにだけ実行されるという「自動初回コード」が記述できる点は、C言語などには見られない特徴的な点である。

6. ビルトイン/ユーティリティパッケージ
---------------------

主に、データベース定義完了後に実行するSQLスクリプトとしてcatproc.sqlがあるが、この時点ではユーティリティパッケージどころか標準SQL関数のCOUNTなども未定義の状態である。これらの機能は通常、データベースカタログを定義するスクリプトcatalog.sqlの実行後に、catproc.sqlというスクリプトを実行することで定義される。 パッケージSTANDARDで定義されているプロシージャ・ファンクションは、呼び出すときにパッケージ名を修飾せずとも呼び出せる。

このcatprocは、OUIでプロダクトインストールとともにデータベース定義も行う場合や、 別途DBCAにてデータベースを定義する場合、DBCAでデータベース定義スクリプトを生成した場合にスクリプトの中に含まれた形で実行されるためあまり意識する必要はないが、定義したデータベースの動作がおかしい場合はこのあたりを見直し、場合によってはスクリプトの再実行を行う必要もある。PSRを適用した場合にもこのスクリプトの再実行をする必要がある。

ユーティリティパッケージはバッファ出力用のDBMS_OUTPUTパッケージやファイル出力用のUTL_FILEパッケージ、応用プログラム間の同期通信に利用できるDBMS_PIPE、DBMS_ALERTパッケージ、動的SQLを実現するためのDBMS_SQLパッケージなどがある。

ユーティリティパッケージの名前はDBMS_またはUTL_で始まるため、ユーザーが新たにパッケージを作成する場合はこれらの名前を避けて作成するべきである。

7. 例
----

※注 DBMS_OUTPUTの出力先を、SQL*PlusというSQLの対話型インターフェースにする場合は、以下のSQL*Plusの設定を行うこと。端末など標準出力への操作はPL/SQLエンジンがネイティブに持つ機能ではなく、ユーティリティ・ツールの機能に依存するためである。

SET SERVEROUTPUT ON

無名ブロックの例

DECLARE hw VARCHAR2 := 'Hello World!'; BEGIN DBMS_OUTPUT.PUT_LINE ; DBMS_OUTPUT.PUT_LINE ; END; /

ストアドプロシージャの例 CREATE OR REPLACE PROCEDURE helloworld  AS /* 定義部は AS から BEGIN となる。*/ hw VARCHAR2 := 'Hello World!'; BEGIN DBMS_OUTPUT.PUT_LINE ; DBMS_OUTPUT.PUT_LINE ; DBMS_OUTPUT.PUT_LINE ; END; /

例外処理の例 DECLARE FNAME EMP.ename%TYPE ; BEGIN SELECT ename INTO fname FROM emp ; EXCEPTION WHEN TOO_MANY_ROWS THEN -- 定義済み例外識別子 DBMS_OUTPUT.PUT_LINE ; WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ; WHEN OTHERS THEN -- その他の例外はすべてここでキャッチ DBMS_OUTPUT.PUT_LINE ; END; /カーソル処理の例 DECLARE CURSOR c1 IS SELECT ename FROM EMP ; FNAME EMP.ename%TYPE ; BEGIN /* カーソルc1をオープンし、全てのレコードをフェッチしきるまでループする フェッチ毎に 表empのカラムenameを取得しつつ表示する */ OPEN c1 ; LOOP FETCH c1 INTO fname ; EXIT WHEN c1%NOTFOUND; -- カーソル状態をチェック DBMS_OUTPUT.PUT_LINE; IF length < 5 THEN -- 文字列の長さチェック DBMS_OUTPUT.PUT_LINE ; ELSE DBMS_OUTPUT.PUT_LINE; END IF ; END LOOP ; CLOSE c1 ; EXCEPTION WHEN OTHERS THEN -- その他の例外はすべてここでキャッチ DBMS_OUTPUT.PUT_LINE ; END ; /

8. 脚注
-----