制御構造

PostgreSQL 8.0.0 文書
前のページ | 巻戻し | 第 35章PL/pgSQL - SQL手続き言語 | 早送り | 次のページ

# 35.7. 制御構造

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

## 35.7.1. 関数からの復帰

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

### 35.7.1.1. `RETURN`

    RETURN expression;

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

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

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

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

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

## 35.7.2. 条件分岐

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

- `IF ... THEN`

- `IF ... THEN ... ELSE`

- `IF ... THEN ... ELSE IF`

- `IF ... THEN ... ELSIF ... THEN ... ELSE`

- `IF ... THEN ... ELSEIF ... THEN ... ELSE`

### 35.7.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;

### 35.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;

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

### 35.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;

### 35.7.2.5. `IF-THEN-ELSEIF-ELSE`

`ELSEIF`は`ELSIF`の別名です。

## 35.7.3. 単純なループ

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

### 35.7.3.1. `LOOP`

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

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

### 35.7.3.2. `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;

### 35.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;

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

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

別の種類の`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` ループであると仮定します。 そのため、`..` 記号の誤記によって、"問い合わせ結果のループ変数は、レコードか行変数でなければなりません" というエラーメッセージが出ることがよくあり、予想される単純な構文エラーとは異なったものとなります。

## 35.7.5. エラーの捕捉

デフォルトでは、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` を使用してはいけません。

前のページ | ホーム | 次のページ
基本的な文 | 上に戻る | カーソル