プログラム・フローの制御

入力順に実行する文であるSQLとは異なり、PL/SQLには、プログラムのフローを制御できる制御文があります。

制御文について

PL/SQLには、条件付き選択文、繰り返し文および順次制御文の3つのカテゴリの制御文があります。

条件付き選択文では、異なるデータ値に対して異なる文を実行できます。条件選択文は、IFCASEです。

Loop文では、一連の異なるデータ値で同じ文を繰り返すことができます。ループ文は、FOR LOOPWHILE LOOPおよび基本LOOPです。EXIT文は、ループの終わりまで制御を転送します。CONTINUE文は、現行のループの反復を終了し、制御を次の反復に転送します。EXITCONTINUEの両方で、条件を指定できるオプションのWHEN句があります。

順次制御文では、指定されたラベル付き文に移動するか、または何も処理を行いません。順次制御文は、GOTOおよびNULLです。

関連項目: PL/SQL制御文の概要は、『Oracle Database PL/SQL言語リファレンス』

IF文の使用

IF文は、ブール式の値に応じて、一連の文を実行またはスキップします。

IF文には次の構文があります。

IF boolean_expression THEN statement [, statement ]
[ ELSIF boolean_expression THEN statement [, statement ] ]...
[ ELSE  statement [, statement ] ]
END IF;

会社が従業員を雇用後の最初の10年間は1年に2回評価し、その後は1年に1回評価すると仮定します。従業員の評価頻度を戻す関数が必要です。例5-4のように、IF文を使用してファンクションの戻り値を判断できます。

EVAL_FREQUENCY関数をEMP_EVALパッケージの本体に追加しますが、仕様には 追加しません。EVAL_FREQUENCYは、仕様にはないため、パッケージに対してローカルであり、パッケージの外部からではなく、パッケージ内の他のサブプログラムでのみ起動できます。

ヒント: SQL文でPL/SQL変数を使用する場合、例5-4の2つ目のSELECT文に示すとおり、変数をサブプログラム名で修飾して、表の列および間違えないようにします。

関連情報:

例5-4 ファンクションの戻り値を判断するIF文

FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
  RETURN PLS_INTEGER
AS
  h_date     EMPLOYEES.HIRE_DATE%TYPE;
  today      EMPLOYEES.HIRE_DATE%TYPE;
  eval_freq  PLS_INTEGER;
BEGIN
  SELECT SYSDATE INTO today FROM DUAL;

  SELECT HIRE_DATE INTO h_date
  FROM EMPLOYEES
  WHERE EMPLOYEE_ID = eval_frequency.emp_id;

  IF ((h_date + (INTERVAL '120' MONTH)) < today) THEN
    eval_freq := 1;
  ELSE
    eval_freq := 2;
  END IF;

  RETURN eval_freq;
END eval_frequency;

CASE文の使用

CASE文は、一連の条件から構成され、対応する文を実行します。

Simple CASE文は、1つの式を評価し、それをいくつかの潜在的な値と比較します。CASE文には、この構文があります。

CASE expression
WHEN value THEN statement
[ WHEN value THEN statement ]...
[ ELSE statement [, statement ]... ]
END CASE;

検索されたCASE文は、複数のブール式を評価し、その値がTRUEである最初のものを選択します。検索されたCASE文の詳細は、Oracle Database PL/SQL言語リファレンスに関する項を参照してください。

ヒント: CASE文またはネストされたIF文を使用する場合、CASE文を使用します。つまり、CASE文は、読取りやすく、効率的です。

従業員が1年に1回のみ評価され、JOB_IDに応じて昇給を提案するEVAL_FREQUENCY関数を必要とすると仮定します。

例5-5に示すとおり、EVAL_FREQUENCYファンクションを変更します。(文字列DBMS_OUTPUT.PUT_LINEを出力する手順の詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照。)

例5-5 出力する文字列を判断するCASE文

FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
  RETURN PLS_INTEGER
AS
  h_date     EMPLOYEES.HIRE_DATE%TYPE;
  today      EMPLOYEES.HIRE_DATE%TYPE;
  eval_freq  PLS_INTEGER;
  j_id       EMPLOYEES.JOB_ID%TYPE;

BEGIN
  SELECT SYSDATE INTO today FROM DUAL;

  SELECT HIRE_DATE, JOB_ID INTO h_date, j_id
  FROM EMPLOYEES
  WHERE EMPLOYEE_ID = eval_frequency.emp_id;

  IF ((h_date + (INTERVAL '12' MONTH)) < today) THEN
    eval_freq := 1;

    CASE j_id
       WHEN 'PU_CLERK' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 8% salary increase for employee # ' || emp_id);
       WHEN 'SH_CLERK' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 7% salary increase for employee # ' || emp_id);
       WHEN 'ST_CLERK' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 6% salary increase for employee # ' || emp_id);
       WHEN 'HR_REP' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 5% salary increase for employee # ' || emp_id);
       WHEN 'PR_REP' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 5% salary increase for employee # ' || emp_id);
       WHEN 'MK_REP' THEN DBMS_OUTPUT.PUT_LINE(
         'Consider 4% salary increase for employee # ' || emp_id);
       ELSE DBMS_OUTPUT.PUT_LINE(
         'Nothing to do for employee #' || emp_id);
    END CASE;
  ELSE
    eval_freq := 2;
  END IF;

  RETURN eval_freq;
END eval_frequency;

関連情報:

FOR LOOP文の使用

FOR LOOP文は、lower_boundからupper_boundの範囲の整数ごとに、一連の文を1回繰り返します。

FOR LOOPの構文は次のとおりです。

FOR counter IN lower_bound..upper_bound LOOP
  statement [, statement ]...
END LOOP;

LOOPとEND LOOPの間のステートメントはカウンタを使用できますが、その値を変更することはできません。

給与の値上げを想定するだけでなく、EVAL_FREQUENCYファンクションを使用して、5年間推定額が増加した場合に給与がどう変わるかをレポートするとします。

例5-6に示すとおり、EVAL_FREQUENCYファンクションを変更します。(文字列DBMS_OUTPUT.PUT_LINEを出力するプロシージャの詳細は、Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンスを参照。)

例5-6 5年後の給与を計算するFOR LOOP文

FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
  RETURN PLS_INTEGER
AS
  h_date      EMPLOYEES.HIRE_DATE%TYPE;
  today       EMPLOYEES.HIRE_DATE%TYPE;
  eval_freq   PLS_INTEGER;
  j_id        EMPLOYEES.JOB_ID%TYPE;
  sal         EMPLOYEES.SALARY%TYPE;
  sal_raise   NUMBER(3,3) := 0;

BEGIN
  SELECT SYSDATE INTO today FROM DUAL;

  SELECT HIRE_DATE, JOB_ID, SALARY INTO h_date, j_id, sal
  FROM EMPLOYEES
  WHERE EMPLOYEE_ID = eval_frequency.emp_id;

  IF ((h_date + (INTERVAL '12' MONTH)) < today) THEN
    eval_freq := 1;

    CASE j_id
      WHEN 'PU_CLERK' THEN sal_raise := 
0.08;
      WHEN 'SH_CLERK' THEN sal_raise := 0.07;
      WHEN 'ST_CLERK' THEN sal_raise := 0.06;
      WHEN 'HR_REP'   THEN sal_raise := 0.05;
      WHEN 'PR_REP'   THEN sal_raise := 0.05;
      WHEN 'MK_REP'   THEN sal_raise := 0.04;
      ELSE NULL;
    END CASE;

    IF (sal_raise != 0) THEN
      BEGIN
        DBMS_OUTPUT.PUT_LINE('If salary ' || sal || ' increases by ' ||
          ROUND((sal_raise * 100),0) ||
          '% each year for 5 years, it will be:');

        FOR i IN 1..5 LOOP
          sal := sal * (1 + sal_raise);
          DBMS_OUTPUT.PUT_LINE(ROUND(sal, 2) || ' after ' || i || ' year(s)');
        END LOOP;
      END;
    END IF;

  ELSE
    eval_freq := 2;
  END IF;

  RETURN eval_freq;
END eval_frequency;

関連情報:

WHILE LOOP文の使用

WHILE LOOP文は、条件がTRUEであるかぎり、一連の文を繰り返す。

WHILE LOOP文の構文は次のとおりです。

WHILE condition LOOP
  statement [, statement ]...
END LOOP;

ノート: LOOPとEND LOOPの間の文によって条件がFALSEにならない場合、WHILE LOOP文は無期限に実行されます。

EVAL_FREQUENCYファンクションでFOR LOOP文ではなくWHILE LOOP文を使用して、提案された給与がJOB_IDの最大給与を超過したときに終了するとします。

例5-7に示すように、EVAL_FREQUENCYファンクションを変更します。(文字列DBMS_OUTPUT.PUT_LINEを出力する手順の詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照。)

例5-7 最大値まで給与を計算するWHILE LOOP文

FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
  RETURN PLS_INTEGER
AS
  h_date      EMPLOYEES.HIRE_DATE%TYPE;
  today       EMPLOYEES.HIRE_DATE%TYPE;
  eval_freq   PLS_INTEGER;
  j_id        EMPLOYEES.JOB_ID%TYPE;
  sal         EMPLOYEES.SALARY%TYPE;
  sal_raise   NUMBER(3,3) := 0;
  sal_max     JOBS.MAX_SALARY%TYPE;

BEGIN
  SELECT SYSDATE INTO today FROM DUAL;

  SELECT HIRE_DATE, j.JOB_ID, SALARY, MAX_SALARY INTO h_date, j_id, sal, sal_max
  FROM EMPLOYEES e, JOBS j
  WHERE EMPLOYEE_ID = eval_frequency.emp_id AND JOB_ID = eval_frequency.j_id;

  IF ((h_date + (INTERVAL '12' MONTH)) < today) THEN
    eval_freq := 1;

    CASE j_id
      WHEN 'PU_CLERK' THEN sal_raise := 0.08;
      WHEN 'SH_CLERK' THEN sal_raise := 0.07;
      WHEN 'ST_CLERK' THEN sal_raise := 0.06;
      WHEN 'HR_REP'   THEN sal_raise := 0.05;
      WHEN 'PR_REP'   THEN sal_raise := 0.05;
      WHEN 'MK_REP'   THEN sal_raise := 0.04;
      ELSE NULL;
    END CASE;

    IF (sal_raise != 0) THEN
      BEGIN
        DBMS_OUTPUT.PUT_LINE('If salary ' || sal || ' increases by ' ||
          ROUND((sal_raise * 100),0) ||
          '% each year, it will be:');

        WHILE sal <= sal_max LOOP
          sal := sal * (1 + sal_raise);
          DBMS_OUTPUT.PUT_LINE(ROUND(sal, 2));
        END LOOP;

        DBMS_OUTPUT.PUT_LINE('Maximum salary for this job is ' || sal_max);
      END;
    END IF;
  ELSE
    eval_freq := 2;
  END IF;

  RETURN eval_freq;
END eval_frequency;

関連情報:

基本LOOPおよびEXIT WHEN文の使用

基本LOOP文は、一連の文を繰り返します。

基本的なLOOP文の構文は次のとおりです。

LOOP
  statement [, statement ]...
END LOOP;

少なくとも1つのステートメントがEXITステートメントである必要があり、そうでない場合、LOOPステートメントは無期限に実行されます。

EXIT WHEN文(オプションのWHEN句があるEXIT文)は、条件がTRUEで、制御をループの終了に転送すると、ループを終了します。

EVAL_FREQUENCYファンクションでは、WHILE LOOP文の最後の反復で、通常、最後に計算された値が最大給与を超過します。

例5-8に示すように、WHILE LOOP文を、EXIT WHEN文を含む基本のLOOP文に変更します。

例5-8 EXIT WHEN文の使用

FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
  RETURN PLS_INTEGER
AS
  h_date      EMPLOYEES.HIRE_DATE%TYPE;
  today       EMPLOYEES.HIRE_DATE%TYPE;
  eval_freq   PLS_INTEGER;
  j_id        EMPLOYEES.JOB_ID%TYPE;
  sal         EMPLOYEES.SALARY%TYPE;
  sal_raise   NUMBER(3,3) := 0;
  sal_max     JOBS.MAX_SALARY%TYPE;

BEGIN
  SELECT SYSDATE INTO today FROM DUAL;

  SELECT HIRE_DATE, j.JOB_ID, SALARY, MAX_SALARY INTO h_date, j_id, sal, sal_max
  FROM EMPLOYEES e, JOBS j
  WHERE EMPLOYEE_ID = eval_frequency.emp_id AND JOB_ID = eval_frequency.j_id;

  IF ((h_date + (INTERVAL '12' MONTH)) < today) THEN
    eval_freq := 1;

    CASE j_id
      WHEN 'PU_CLERK' THEN sal_raise := 0.08;
      WHEN 'SH_CLERK' THEN sal_raise := 0.07;
      WHEN 'ST_CLERK' THEN sal_raise := 0.06;
      WHEN 'HR_REP'   THEN sal_raise := 0.05;
      WHEN 'PR_REP'   THEN sal_raise := 0.05;
      WHEN 'MK_REP'   THEN sal_raise := 0.04;
      ELSE NULL;
    END CASE;

    IF (sal_raise != 0) THEN
      BEGIN
        DBMS_OUTPUT.PUT_LINE('If salary ' || sal || ' increases by ' ||
          ROUND((sal_raise * 100),0) ||
          '% each year, it will be:');

        LOOP
          sal := sal * (1 + sal_raise);
          EXIT WHEN sal > sal_max;
          DBMS_OUTPUT.PUT_LINE(ROUND(sal,2));
        END LOOP;

        DBMS_OUTPUT.PUT_LINE('Maximum salary for this job is ' || sal_max);
      END;
    END IF;
  ELSE
    eval_freq := 2;
  END IF;

  RETURN eval_freq;
END eval_frequency;

関連情報: