★PostgreSQLカンファレンス2024 12月6日開催/チケット販売中★
他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

19.6. 制御構造

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

19.6.1. 関数からの復帰

RETURN expression;

式 (expression) の前の RETURN は、セットを返さない PL/pgSQL 関数からの戻り値を得るために使用されます。 関数は終了し、expression の値が呼び出し元に返されます。

複数 (行) の値を返すには、レコードまたは行変数を expression として記述する必要があります。 スカラ型を返す際は、どの式でも使用可能です。 この式の結果は、自動的に関数宣言時の戻り値型にキャストされます。 (関数が void 型を返すように宣言した場合、式は省略可能ですし、どのような場合でも無視されます。)

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

PL/pgSQL 関数が SETOF sometype を返すように宣言した場合、後続の処理が多少違います。 この場合、戻り値の個々の項目は、RETURN NEXT コマンドで指定されます。そして、引数のない最後の RETURN コマンドにより、関数が実行を終了したことが示されます。 RETURN NEXT は、スカラ型および複合型の両方で使用することができます。複合型の場合、結果の「テーブル」全体が返されます。 RETURN NEXT を使用する関数は、以下のような形式で呼び出されます。

SELECT * FROM some_func();

つまり、関数は FROM 句でテーブルのソースとして使用されます。

RETURN NEXT expression;

実際には、RETURN NEXT は関数から値を返しているのではありません。単に式の値 (もしくは返されるデータ型に応じてレコード変数または行変数) を保存しているだけです。 そして、その実行は PL/pgSQL 関数内の次の文に継続します。 RETURN NEXT コマンドが連続して実行されると、結果セットが作成されます。 最後の RETURN (引数を設定しない必要があります) により、関数の終了を制御します。

Note: 上記のように、PL/pgSQL における RETURN NEXT の現在の実装では、関数から返される前に結果セット全体を保管します。 これにより、PL/pgSQL 関数が非常に大量の結果セットを返した場合、パフォーマンスが低下する可能性があります。 メモリの消耗を避けるため、データはディスクに書き込まれます。しかし、関数自体は、結果セット全体が生成されるまでは返りません。 将来の PL/pgSQL のバージョンでは、この制限を受けずにセットを返す関数をユーザが定義できるようになるかもしれません。 現在、ディスクに書きこまれるデータの開始点は、SORT_MEM 設定変数によって制御されています。 大量の結果セットを保管するのに十分なメモリがある場合、管理者はこのパラメータの値を大きくすることを考慮すべきです。

19.6.2. 条件分岐

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

19.6.2.1. IF-THEN

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;

19.6.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;

19.6.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 文が必要です。これにより正常に動作できますが、検査すべき候補が多くある場合は長たらしくなります。

19.6.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;

最後の ELSE 節は省略可能です。

19.6.3. 単純なループ

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

19.6.3.1. LOOP

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

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

19.6.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

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

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

LOOP
    -- 何らかの演算
    IF count > 0 THEN
        EXIT;  -- ループの終了
    END IF;
END LOOP;

LOOP
    -- 何らかの演算
    EXIT WHEN count > 0;
END LOOP;

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

19.6.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;

19.6.3.4. FOR (整数 for ループ)

[<<label>>]
FOR name IN [ REVERSE ] expression .. expression LOOP
    statements
END LOOP;

この種類のFOR は整数値の範囲を繰り返すループを生成します。name 変数は整数型として自動的に定義され、ループ内部のみで存在します。範囲の下限、上限として与えられる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;

19.6.4. 問い合わせ結果の繰返し

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

[<<label>>]
FOR record | row IN select_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 | row IN EXECUTE text_expression LOOP
    statements
END LOOP;

これは、元とする SELECT 文が文字列式で指定される点を除き、前の形式と似ています。 この式は FOR ループの各エントリで評価され、再計画が行われます。これにより、プログラマは、通常の EXECUTE 文と同じように事前に計画された問い合わせによる高速性と、動的な問い合わせの持つ柔軟性を選択することができます。

Note: PL/pgSQL パーサは現在この(整数を返すかレコードを返すかという)2種類の FOR ループを、FOR の後で指定される目的変数がレコード/行変数として宣言されているかどうかで区別しています。もしなければ、整数の FOR ループであると仮定します。 実際の問題点が、FOR の変数名の書き間違えといったことである場合、これはどちらかというと直観的でないエラーメッセージを引き起こします。