制御構造はおそらくPL/pgSQLのもっとも有用(かつ重要)な部分です。 PL/pgSQLの制御構造を使用して、PostgreSQLのデータを非常に柔軟、強力に操作することができます
関数からデータを返すために使用できるコマンドが2つあります。 RETURNおよびRETURN NEXTです。
RETURN expression;
式を後に付けたRETURNは関数を終了し、expressionの値を呼出し元に返します。 この形式は集合を返さないPL/pgSQL関数で使用されます。
スカラ型を返す場合は任意の式を使用することができます。 この式の結果は、代入のところで説明したように、自動的に関数の戻り値の型にキャストされます。 複合(行)値を返すためには、expressionとしてレコードもしくは行変数を記述しなければなりません。
関数の戻り値は未定義とさせたままにすることはできません。 制御が、RETURN文が見つからない状態で関数の最上位のブロックの終わりまで達した時、実行時エラーが発生します。
voidを返すように関数を宣言した場合でもRETURNを指定する必要があります。 しかし、この場合RETURNに続く式は省略可能で、あったとしても無視されます。
RETURN NEXT expression;
PL/pgSQL関数がSETOF sometypeを返すように宣言した場合、後続の処理が多少違います。 この場合、戻り値の個々の項目は、RETURN NEXTコマンドで指定されます。 そして、引数のない最後のRETURNコマンドにより、関数が実行を終了したことが示されます。 RETURN NEXTは、スカラ型および複合型の両方で使用することができます。 複合型の場合、結果の"テーブル"全体が返されます。
RETURN NEXTを使用する関数は、以下のような形式で呼び出されます。
SELECT * FROM some_func();
つまり、この関数は FROM 句でテーブルのソースとして使用されなければなりません。
実際には、RETURN NEXT は関数から戻りません。 単に式の値を保存しているだけです。 そして、その実行は PL/pgSQL 関数内の次の文に継続します。 RETURN NEXT コマンドが連続して実行されると、結果集合が作成されます。 最後の、引数を持ってはならない RETURN により、関数の終了を制御します。
注意: 上記のように、PL/pgSQL におけ るRETURN NEXT の現在の実装では、関数から返される前に結果集合全体を保管します。 これにより、PL/pgSQL 関数が非常に大量の結果セットを返した場合、性能が低下する可能性があります。 メモリの消耗を避けるため、データはディスクに書き込まれます。 しかし、関数自体は結果集合全体が生成されるまでは返りません。 将来の PL/pgSQL のバージョンでは、この制限を受けずに集合を返す関数をユーザが定義できるようになるかもしれません。 現在、ディスクに書き込まれるデータの開始点は work_mem 設定変数によって制御されています。 大量の結果集合を保管するのに十分なメモリがある場合、管理者はこのパラメータの値を大きくすることを考慮すべきです。
IF 文はある条件に基づいてコマンドを実行させます。 PL/pgSQL には、以下のような 5 つの IF の形式があります。
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF ... THEN ... ELSEIF ... THEN ... ELSE
IF boolean-expression THEN statements END IF;
IF-THEN文は、最も単純なIFの形式です。THENとEND IFの間の文が条件が真の場合に実行されます。 さもなければそれらは飛ばされます。
例
IF v_user_id <> 0 THEN UPDATE users SET email = v_email WHERE user_id = v_user_id; END IF;
IF boolean-expression THEN statements ELSE statements END IF;
IF-THEN-ELSE文はIF-THENに加え、条件評価が偽の場合に実行すべき文の集合を指定させることができます。
例
IF parentid IS NULL OR parentid = '' THEN RETURN fullname; ELSE RETURN hp_true_filename(parentid) || '/' || fullname; END IF;
IF v_count > 0 THEN INSERT INTO users_count (count) VALUES (v_count); RETURN 't'; ELSE RETURN 'f'; END IF;
以下の例のようにIF文は入れ子にすることができます。
IF demo_row.sex = 'm' THEN pretty_sex := 'man'; ELSE IF demo_row.sex = 'f' THEN pretty_sex := 'woman'; END IF; END IF;
この形式を使用する場合、実際にIF文を外側のIF文のELSEの部分の内側に入れ子にしています。 従って、入れ子にしたIF毎に1つのEND IF文が、その親となるIF-ELSEに1つのEND IF文が必要です。 これにより正常に動作できますが、検査すべき候補が多くある場合は長たらしくなります。 その結果、次の形式です。
IF boolean-expression THEN statements [ ELSIF boolean-expression THEN statements [ ELSIF boolean-expression THEN statements ...]] [ ELSE statements ] END IF;
IF-THEN-ELSIF-ELSEは、ある文に多くの代替手段がある場合のチェックに、より便利な方法を提供します。 形としては、IF-THEN-ELSE-IF-THENコマンドを入れ子にしたものと同じですが、必要なEND IFは1つだけです。
以下に例を示します。
IF number = 0 THEN result := 'zero'; ELSIF number > 0 THEN result := 'positive'; ELSIF number < 0 THEN result := 'negative'; ELSE -- ふうむ、残る唯一の可能性はその数が NULL であることだ result := 'NULL'; END IF;
LOOP、EXIT、WHILE、FOR文を使用して、PL/pgSQL関数で、コマンド群を繰り返すことができます。
[<<label>>]
LOOP
statements
END LOOP;
LOOPは、EXIT文またはRETURN文によって終了されるまで無限に繰り返される、条件無しのループを定義します。 ラベルオプションは、入れ子状ループ内のEXIT文で、どのレベルの入れ子が終了するかを指定するために使用されます。
EXIT [ label ] [ WHEN expression ];
labelが指定されない場合、最も内側のループを終らせ、END LOOPの次の文がその後に実行されます。 label が指定された場合、それは現在のループ、もしくは、入れ子になったループやブロックの外側のレベルのラベルである必要があります。 その後、指名されたループまたはブロックを終らせ、そのループまたはブロックの対応するENDの次の文に制御を移します。
WHENがある場合、指定された条件が真の場合のみループの終了が起こります。 さもなければ、EXITの後の行に制御が移ります。
EXIT は、条件無しのループに限らず、あらゆるループからの即時の脱出のために使用できます。
例
LOOP -- 何らかの演算 IF count > 0 THEN EXIT; -- ループを抜け出す END IF; END LOOP; LOOP -- 何らかの演算 EXIT WHEN count > 0; -- 上例と同じ結果 END LOOP; BEGIN -- 何らかの演算 IF stocks > 100000 THEN EXIT; -- これにより BEGIN ブロックを抜け出す END IF; END;
[<<label>>]
WHILE expression LOOP
statements
END LOOP;
WHILE文は条件式の評価が真である間、文の並びを繰り返します。 条件は、ループ本体に入る前にのみチェックされます。
以下に例を示します。
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP -- ここで演算をいくつか行います。 END LOOP; WHILE NOT boolean_expression LOOP -- ここで演算をいくつか行います。 END LOOP;
[<<label>>] FOR name IN [ REVERSE ] expression .. expression LOOP statements END LOOP;
この形式のFORは整数値の範囲を繰り返すループを生成します。 name変数はinteger型として自動的に定義され、ループ内部のみで存在します。 範囲の下限、上限として与えられる2つの式はループに入った時に一度だけ評価されます。 繰返し刻みは通常1ですが、REVERSEが指定された場合は-1となります。
整数FORループの例を以下に示します。
FOR i IN 1..10 LOOP -- ここで演算をいくつか行います。 RAISE NOTICE 'i is %', i; END LOOP; FOR i IN REVERSE 10..1 LOOP -- ここで演算をいくつか行います。 END LOOP;
下限が上限よりも大きい(REVERSEの場合はより小さい)場合、ループ本体は全く実行されません。 エラーは発生しません。
別の種類のFORループを使用して、問い合わせの結果を繰返し、そのデータを扱うことができます。 以下に構文を示します。
[<<label>>]
FOR record_or_row IN query LOOP
statements
END LOOP;
レコードまたは行変数には、正常にquery(SELECTコマンド)の結果の全ての行が代入され、各行に対してループ本体が実行されます。 以下に例を示します。
CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$ DECLARE mviews RECORD; BEGIN PERFORM cs_log('Refreshing materialized views...'); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP -- ここで"mviews"はcs_materialized_viewsの1つのレコードを持ちます PERFORM cs_log('Refreshing materialized view ' || quote_ident(mviews.mv_name) || ' ...'); EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name); EXECUTE 'INSERT INTO ' || quote_ident(mviews.mv_name) || ' ' || mviews.mv_query; END LOOP; PERFORM cs_log('Done refreshing materialized views.'); RETURN 1; END; $$ LANGUAGE plpgsql;
このループがEXIT文で終了した場合、最後に割り当てられた行の値はループを抜けた後でもアクセスすることができます。
FOR-IN-EXECUTE 文は行を繰り返すもう一つの方法です。
[<<label>>]
FOR record_or_row IN EXECUTE text_expression LOOP
statements
END LOOP;
これは、元とするSELECT文が文字列式で指定される点を除き、前の形式と似ています。 この式はFORループの各エントリで評価され、再計画が行われます。 これにより、プログラマは、通常のEXECUTE文と同じように事前に計画された問い合わせによる高速性と、動的な問い合わせの持つ柔軟性を選択することができます。
注意: 現在 PL/pgSQL パーサはこの (整数または問い合わせ結果という) 2 種類の FOR ループを、次のような方法で区別しています。 それは IN と LOOP の間に、カッコを含むときはカッコの外側に、.. 記号が存在するか否かというものです。 もし .. 記号がなければ、問い合わせ結果 FOR ループであると仮定します。 そのため、.. 記号の誤記によって、"問い合わせ結果のループ変数は、レコードか行変数でなければなりません" というエラーメッセージが出ることがよくあり、予想される単純な構文エラーとは異なったものとなります。
デフォルトでは、PL/pgSQL 関数の内部で発生したエラーは関数の実行を中止し、実際に周辺のトランザクションをアボートします。 BEGIN ブロックおよび EXCEPTION 句を使用すれば、エラーを捕捉してその状態から回復できます。 その構文は通常の BEGIN ブロックの構文を拡張したものです。
[ <<label>> ] [ DECLARE declarations ] BEGIN statements EXCEPTION WHEN condition [ OR condition ... ] THEN handler_statements [ WHEN condition [ OR condition ... ] THEN handler_statements ... ] END;
エラーが発生しないとき、この形式のブロックは単に全ての statements を実行し、END の次の文に制御が移ります。 しかし、statements の内部でエラーが発生すると、それ以後の statements における処理は中断され、EXCEPTION に示した一覧に制御が移ります。 そして一覧の中から、発生したエラーと合致する最初の condition を探します。 合致するものがあれば、対応する handler_statements を実行し、END の次の文に制御が移ります。 合致するものがなければ、EXCEPTION 句が存在しないのと同じで、エラーは外側に波及します。 EXCEPTION を含んだ外側のブロックはエラーを捕捉できますが、失敗すると関数の処理は中断されます。
全ての状態名は付録Aに示すことができます。 分類名はそこに分類される全てのエラーに合致します。 OTHERS という特別の状態名は QUERY_CANCELED 以外の全てのエラーに合致します。 (QUERY_CANCELED を名前で捕捉することは可能ですが、賢明ではありません。) 状態名は、大文字と小文字を区別しません。
エラーが該当する handler_statements 内部で新たに発生したとき、EXCEPTION 句はそのエラーを捕捉できずエラーは外側に波及します。 なお、外側の EXCEPTION 句は、そのエラーを捕捉できます。
EXCEPTION 句がエラーを捕捉したとき、PL/pgSQL 関数のローカル変数はエラーが起こった後の状態を保ちます。 しかし、ブロック内部における永続的なデータベースの状態は、ロールバックされます。 そのような例を以下に示します。
INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones'); BEGIN UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones'; x := x + 1; y := x / 0; EXCEPTION WHEN division_by_zero THEN RAISE NOTICE 'caught division_by_zero'; RETURN x; END;
制御が変数 y の代入に移ると、ゼロで割るため division_by_zero エラーとなり、EXCEPTION 句がそのエラーを捕捉します。 RETURN 文による関数の戻り値は、1 を加算した後の x の値となりますが、UPDATE コマンドによる結果はロールバックされます。 しかし、BEGIN ブロックの前の INSERT コマンドはロールバックされません。 したがって、データベースの内容の最終結果は Tom Jones であり、Joe Jones ではありません。
ティップ: EXCEPTION 句を含んだブロックの実行に要する時間は、含まないブロックに比べてとても長くなります。 したがって、必要のないときに EXCEPTION を使用してはいけません。