37.7. 制御構造

制御構造はおそらくPL/pgSQLのもっとも有用(かつ重要)な部分です。 PL/pgSQLの制御構造を使用して、PostgreSQLのデータを非常に柔軟、強力に操作することができます。

37.7.1. 関数からの復帰

関数からデータを返すために使用できるコマンドが2つあります。 RETURNおよびRETURN NEXTです。

37.7.1.1. RETURN

RETURN expression;

式を後に付けたRETURNは関数を終了し、expressionの値を呼出し元に返します。 この形式は集合を返さないPL/pgSQL関数で使用されます。

スカラ型を返す場合は任意の式を使用することができます。 この式の結果は、代入のところで説明したように、自動的に関数の戻り値の型にキャストされます。 複合(行)値を返すためには、expressionとしてレコードもしくは行変数を記述しなければなりません。

関数の戻り値は未定義とさせたままにすることはできません。 制御が、RETURN文が見つからない状態で関数の最上位のブロックの終わりまで達した時、実行時エラーが発生します。

voidを返すように関数を宣言した場合でもRETURNを指定する必要があります。 しかし、この場合RETURNに続く式は省略可能で、あったとしても無視されます。

37.7.1.2. RETURN NEXT

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のバージョンでは、この制限を受けずに集合を返す関数をユーザが定義できるようになるかもしれません。 現在、ディスクに書き込まれるデータの開始点は、SORT_MEM設定変数によって制御されています。 大量の結果集合を保管するのに十分なメモリがある場合、管理者はこのパラメータの値を大きくすることを考慮すべきです。

37.7.2. 条件分岐

IF文はある条件に基づいてコマンドを実行させます。 PL/pgSQLには、以下のような4つのIFの形式があります。

37.7.2.1. IF-THEN

IF boolean-expression THEN
    statements
END IF;

IF-THEN文は、最も単純なIFの形式です。THENEND IFの間の文が条件が真の場合に実行されます。 さもなければそれらは飛ばされます。

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

37.7.2.2. IF-THEN-ELSE

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;

37.7.2.3. IF-THEN-ELSE 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文が必要です。 これにより正常に動作できますが、検査すべき候補が多くある場合は長たらしくなります。 その結果、次の形式です。

37.7.2.4. IF-THEN-ELSIF-ELSE

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;

37.7.3. 単純なループ

LOOPEXITWHILEFOR文を使用して、PL/pgSQL関数で、コマンド群を繰り返すことができます。

37.7.3.1. LOOP

[<<label>>]
LOOP
    statements
END LOOP;

LOOPは、EXIT文またはRETURN文によって終了されるまで無限に繰り返される、条件無しのループを定義します。 ラベルオプションは、入れ子状ループ内のEXIT文で、どのレベルの入れ子が終了するかを指定するために使用されます。

37.7.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

labelが指定されない場合、最も内側のループを終らせ、END LOOPの次の文がその後に実行されます。 label が指定された場合、それは現在のループ、もしくは、入れ子になったループやブロックの外側のレベルのラベルである必要があります。 その後、指名されたループまたはブロックを終らせ、そのループまたはブロックの対応するENDの次の文に制御を移します。

WHENがある場合、指定された条件が真の場合のみループの終了が起こります。 さもなければ、EXITの後の行に制御が移ります。

LOOP
    -- 何らかの演算
    IF count > 0 THEN
        EXIT;  - exit loop
    END IF;
END LOOP;

LOOP
    -- 何らかの演算
    EXIT WHEN count > 0;  -- same result as previous example
END LOOP;

BEGIN
    -- 何らかの演算
    IF stocks > 100000 THEN
        EXIT;  -- 不正です。LOOPの外側では EXIT は使用できません。
    END IF;
END;

37.7.3.3. WHILE

[<<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;

37.7.3.4. FOR (整数FORループ)

[<<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の場合はより小さい)場合、ループ本体は全く実行されません。 エラーは発生しません。

37.7.4. 問い合わせ結果による繰返し

別の種類のFORループを使用して、問い合わせの結果を繰返し、そのデータを扱うことができます。 以下に構文を示します。

[<<label>>]
FOR record_or_row IN query LOOP
    statements
END LOOP;

レコードまたは行変数には、正常に問い合わせ(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ループを、FORの後で指定される目的変数がレコード/行変数として宣言されているかどうかで区別しています。 もしなければ、整数FORループであると仮定します。 実際の問題点が、FORの後の変数名の書き間違えといったことである場合、これはどちらかというと直観的でないエラーメッセージを引き起こします。 よくあるメッセージはSQL式の終端に".."が存在しないのようなものです。