【JPUG主催】PostgreSQLカンファレンス2020【11月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

23.6. 制御構造

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

23.6.1. 関数からの復帰

RETURN expression;

関数は終了し、expression の値が上位のエグゼキュータに返されます。この式の結果は自動的に関数宣言時の戻り値型にキャストされます。

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

23.6.2. 条件分岐

IF 文はある条件に基づいてコマンドを実行させます。 PL/pgSQL の IFの形式には、IF-THEN, IF-THEN-ELSE,IF-THEN-ELSE IF, and IF-THEN-ELSIF-THEN-ELSE という4つがあります。

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

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

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

23.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
    -- hmm, the only other possibility is that number IS NULL
    result := ''NULL'';
END IF;

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

23.6.3. 単純なループ

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

23.6.3.1. LOOP

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

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

23.6.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

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

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

Examples:

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;

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

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

23.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 の変数名を書き間違えたといった、本当の問題の時、これはどちらかというと非直観的なエラーメッセージを引き起こします。