控制程序流

与以输入顺序运行语句的 SQL 不同的 PL/SQL 具有控制语句,可用于控制程序流。

关于控制语句

PL/SQL 有三种类型的控制语句:条件选择语句、循环语句和顺序控制语句。

条件选择语句,这种语句可用于对不同的数据值执行不同的语句。条件选择语句为 IFCASE

循环语句,这种语句可用于对一系列不同的数据值重复执行相同语句。循环语句有 FOR LOOPWHILE LOOP 和基本 LOOPEXIT 语句用于将控制权转到循环结束位置。CONTINUE 语句用于退出当前循环迭代,并将控制权转到下一次迭代。EXITCONTINUE 都具有可选的 WHEN 子句,您可以在该子句中指定条件。

顺序控制语句允许您转至指定的有标签语句或不执行任何操作。顺序控制语句为 GOTONULL

另请参阅: Oracle Database PL/SQL Language Reference(了解 PL/SQL 控制语句的概述)

使用 IF 语句

IF 语句可根据布尔表达式的值执行或跳过一系列语句。

IF 语句的语法如下:

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

假设您的公司在前 10 年的雇佣期中每年进行两次员工评估,但其后每年只进行一次员工评估。您想通过一个函数返回某个员工的评估频率。这时可以使用 IF 语句来确定函数的返回值,如 Example 5-4 中所示。

请将此 EVAL_FREQUENCY 函数添加到 EMP_EVAL 程序包的主体,而不要添加到其规范中。由于它不在规范中,因此 EVAL_FREQUENCY 是该程序包的本地程序,它只能由程序包中的其他子程序调用,而不能从程序包外部进行调用。

提示:在 SQL 语句中使用 PL/SQL 变量时,如 Example 5-4 中的第二个 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 语句从一系列条件中进行选择,然后执行相应语句。

简单 CASE 语句可对一个表达式求值,然后将该值与该若干个可能的值进行比较。其语法如下:

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

搜索 CASE 语句可以求出多个布尔表达式,然后选择第一个值为 TRUE 的表达式。有关搜索 CASE 语句的信息,请参阅 Oracle Database PL/SQL Language Reference

提示:当您既可以使用 CASE 语句也可以选择使用嵌套的 IF 语句时,请使用 CASE 语句,因为其可读性更高,并且更加有效。

假设某员工每年只进行一次评估,您希望 EVAL_FREQUENCY 函数能够根据 JOB_ID 建议一个加薪。

更改 EVAL_FREQUENCY 函数,如 Example 5-5 中所示。(有关字符串输出过程 DBMS_OUTPUT.PUT_LINE 的信息,请参阅 Oracle Database PL/SQL Packages and Types Reference。)

用于确定要输出的字符串的 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 范围内的每个整数重复执行某个语句序列。

FOR LOOP 的语法为:

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

在 LOOP 和 END LOOP 之间的语句可以使用计数器,但不能更改其值。

假设您不仅希望 EVAL_FREQUENCY 函数建议一个加薪,而且希望它报告按照建议的数额加五年后的薪金水。

更改 EVAL_FREQUENCY 函数,如 Example 5-6 中所示。(有关字符串的输出过程 DBMS_OUTPUT.PUT_LINE 的信息,请参阅 Oracle Database PL/SQL Packages and Types Reference。)

用于计算五年后的薪金的 FOR LOOP 语句示例 5-6

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 函数使用 WHILE LOOP 语句而不使用 FOR LOOP 语句,并当建议薪水超过 JOB_ID 的最高薪水后结束。

更改 EVAL_FREQUENCY 函数,如 Example 5-7 中所示。(有关字符串输出过程 DBMS_OUTPUT.PUT_LINE 的信息,请参阅 Oracle Database PL/SQL Packages and Types Reference。)

示例 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;

至少一个语句必须是 EXIT 语句;否则,LOOP 语句将无限期运行。

EXIT WHEN 语句(带有可选 WHEN 子句的 EXIT 语句)将在条件为 TRUE 时退出循环,并将控制权转至循环结束。

在 EVAL_FREQUENCY 函数中,WHILE LOOP 语句的最后一次迭代所计算得到的值通常会超过最高薪水。

将 WHILE LOOP 语句更改为包含一个 EXIT WHEN 语句的基本 LOOP 语句,如下例所示 Example 5-8

示例 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;

另请参见: