%TYPE
INDEX BY PLS_INTEGER;
TYPE hire_date_aat IS TABLE OF employee.hire_date%TYPE
INDEX BY PLS_INTEGER;
employee_ids employee_aat;
salaries salary_aat;
hire_dates hire_date_aat;
approved_employee_ids employee_aat;
denied_employee_ids employee_aat;
denied_salaries salary_aat;
denied_hire_dates hire_date_aat;
PROCEDURE retrieve_employee_info
IS
BEGIN
SELECT employee_id, salary, hire_date
BULK COLLECT INTO employee_ids, salaries, hire_dates
FROM employee
WHERE department_id = dept_in;
END;
PROCEDURE partition_by_eligibility
IS
BEGIN
FOR indx IN employee_ids.FIRST .. employee_ids.LAST
LOOP
IF comp_analysis.is_eligible (employee_ids (indx))
THEN
approved_employee_ids (indx) := employee_ids (indx);
ELSE
denied_employee_ids (indx) := employee_ids (indx);
denied_salaries (indx) := salaries (indx);
denied_hire_dates (indx) := hire_dates (indx);
END IF;
END LOOP;
END;
PROCEDURE add_to_history
IS
BEGIN
FORALL indx IN denied_employee_ids.FIRST .. denied_employee_ids.LAST
INSERT INTO employee_history
(employee_id
, salary
, hire_date, activity
)
VALUES (denied_employee_ids (indx)
, denied_salaries (indx)
, denied_hire_dates (indx), 'RAISE DENIED'
);
END;
PROCEDURE give_the_raise
IS
BEGIN
FORALL indx IN approved_employee_ids.FIRST .. approved_employee_ids.LAST
UPDATE employee
SET salary = newsal
WHERE employee_id = approved_employee_ids (indx);
END;
BEGIN
retrieve_employee_info;
partition_by_eligibility;
add_to_history;
give_the_raise;
END give_raises_in_department;
扫一眼清单1 和清单2 就会清楚地认识到:改用集合和批量处理方法将增加代码量和复杂性。但是,如果你需要大幅度提升性能,这还是值得
的。下面,我们不看这些代码,我们来看一看当使用FORALL时,用什么来处理CURSOR FOR循环内的条件逻辑。
定义集合类型与集合
在清单 2中,声明段的第一部分(第6行至第11行)定义了几种不同的集合类型,与我将从员工表检索出的列相对应。我更喜欢基于employee%
ROWTYPE来声明一个集合类型,但是FORALL还不支持对某些记录集合的操作,在这样的记录中,我将引用个别字段。所以,我还必须为员工ID、
薪金和雇用日期分别声明其各自的集合。
接下来为每一列声明所需的集合(第13行至第21行)。首先定义与所查询列相对应的集合(第13行至第15行):
employee_ids employee_aat;
salaries salary_aat;
hire_dates hire_date_aat;
然后我需要一个新的集合,用于存放已被批准加薪的员工的ID(第17行):
approved_employee_ids employee_aat;
最后,我再为每一列声明一个集合(第19行至第21行),用于记录没有加薪资格的员工:
denied_employee_ids employee_aat;
denied_salaries salary_aat;
denied_hire_dates hire_date_aat;
深入了解代码
数据结构确定后,我们现在跳过该程序的执行部分(第72行至第75行),了解如何使用这些集合来加速进程。
retrieve_employee_info;
partition_by_eligibility;
add_to_history;
give_the_raise;
我编写此程序使用了逐步细化法(也被称为"自顶向下设计")。所以执行部分不是很长,也不难理解,只有四行,按名称对过程中的每一步进
行了描述。首先检索员工信息(指定部门的所有员工)。然后进行划分,将要加薪和不予加薪的员工区分出来。完成之后,我就可以将那些不
予加薪的员工添加至员工历史表中,对其他员工进行加薪。
以这种方式编写代码使最终结果的可读性大大增强。因而我可以深入到该程序中对我有意义的任何部分。
有了已声明的集合,我现在就可以使用BULK COLLECT来检索员工信息(第23行至第30行)。这一部分有效地替代了CURSOR FOR循环。至此,数
据被加载到集合中。
划分逻辑(第32行至第46行)要求对刚刚填充的集合中的每一行进行检查,看其是否符合加薪条件。如果符合,我就将该员工ID从查询填充的
集合复制到符合条件的员工的集合。如果不符合,则复制该员工ID、薪金和雇用日期,因为这些都需要插入到employee_history表中。
初始数据现在已被分为两个集合,可以将其分别用作两个不同的FORALL语句(分别从第51行和第66行开始)的驱动器。我将不合格员工的集合
中的数据批量插入到employee_history(add_to_history)表中,并通过give_the_raise过程,在employee表中批量更新合格员工的信息。
最后再仔细地看一看add_to_history(第48行至第61行),以此来结束对这个重新编写的程序的分析。FORALL语句(第51行)包含一个IN子句
,它指定了要用于批量INSERT的行号范围。在对程序进行第二次重写的说明中,我将把用于定义范围的集合称为"驱动集合"。但在
add_to_history的这一版本中,我简单地假定: 使用在denied_employee_ids中定义的所有行。在