当前位置: 首页 > news >正文

【Oracle】存储过程

在这里插入图片描述

个人主页:Guiat
归属专栏:Oracle

在这里插入图片描述

文章目录

  • 1. 存储过程基础概述
    • 1.1 存储过程的概念与特点
    • 1.2 存储过程的组成结构
    • 1.3 存储过程的优势
  • 2. 基础存储过程
    • 2.1 简单存储过程
      • 2.1.1 创建第一个存储过程
      • 2.1.2 带变量的存储过程
    • 2.2 带参数的存储过程
      • 2.2.1 输入参数 (IN)
      • 2.2.2 输出参数 (OUT)
      • 2.2.3 输入输出参数 (IN OUT)
  • 3. 高级存储过程特性
    • 3.1 游标的使用
      • 3.1.1 显式游标
      • 3.1.2 游标FOR循环
    • 3.2 异常处理
      • 3.2.1 预定义异常处理
      • 3.2.2 用户定义异常
  • 4. 存储过程的高级应用
    • 4.1 动态SQL
      • 4.1.1 使用EXECUTE IMMEDIATE
      • 4.1.2 动态查询构建器
    • 4.2 批量处理
      • 4.2.1 BULK COLLECT和FORALL
      • 4.2.2 错误处理的批量操作
  • 5. 存储过程的调试与优化
    • 5.1 调试技术
      • 5.1.1 使用DBMS_OUTPUT进行调试
      • 5.1.2 性能监控和分析
    • 5.2 存储过程优化
      • 5.2.1 SQL优化技巧
      • 5.2.2 内存和资源优化
  • 6. 存储过程的安全性
    • 6.1 权限管理
      • 6.1.1 存储过程权限控制

正文

1. 存储过程基础概述

存储过程是预编译的SQL和PL/SQL代码块,存储在数据库中,可以重复调用执行。它是Oracle数据库中实现复杂业务逻辑的重要工具。

1.1 存储过程的概念与特点

Oracle存储过程
预编译代码
服务器端执行
可重用性
安全性
编译一次多次执行
执行效率高
减少网络传输
在数据库服务器执行
减少客户端负载
集中业务逻辑
模块化编程
代码复用
维护简便
权限控制
SQL注入防护
数据访问封装

1.2 存储过程的组成结构

存储过程结构
声明部分 DECLARE
执行部分 BEGIN
异常处理 EXCEPTION
结束标记 END
变量声明
游标声明
嵌套过程声明
SQL语句
PL/SQL语句
业务逻辑
预定义异常
用户定义异常
异常处理逻辑

1.3 存储过程的优势

  • 性能优化:预编译,执行效率高
  • 网络优化:减少客户端与服务器间的通信
  • 安全性:封装数据访问,防止SQL注入
  • 维护性:集中管理业务逻辑
  • 可重用性:一次编写,多处调用

2. 基础存储过程

2.1 简单存储过程

2.1.1 创建第一个存储过程

-- 创建简单的存储过程
CREATE OR REPLACE PROCEDURE hello_world
AS
BEGINDBMS_OUTPUT.PUT_LINE('Hello, Oracle World!');DBMS_OUTPUT.PUT_LINE('当前时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END hello_world;
/-- 调用存储过程
SET SERVEROUTPUT ON
EXEC hello_world;-- 或者使用CALL语句
CALL hello_world();-- 在PL/SQL块中调用
BEGINhello_world;
END;
/

2.1.2 带变量的存储过程

-- 创建包含变量的存储过程
CREATE OR REPLACE PROCEDURE employee_count_info
ASv_total_count NUMBER;v_active_count NUMBER;v_avg_salary NUMBER;v_max_salary NUMBER;v_min_salary NUMBER;
BEGIN-- 获取员工统计信息SELECT COUNT(*),COUNT(CASE WHEN hire_date >= ADD_MONTHS(SYSDATE, -12) THEN 1 END),ROUND(AVG(salary), 2),MAX(salary),MIN(salary)INTO v_total_count, v_active_count, v_avg_salary, v_max_salary, v_min_salaryFROM employees;-- 输出统计信息DBMS_OUTPUT.PUT_LINE('=== 员工统计信息 ===');DBMS_OUTPUT.PUT_LINE('总员工数: ' || v_total_count);DBMS_OUTPUT.PUT_LINE('近一年入职: ' || v_active_count);DBMS_OUTPUT.PUT_LINE('平均工资: $' || v_avg_salary);DBMS_OUTPUT.PUT_LINE('最高工资: $' || v_max_salary);DBMS_OUTPUT.PUT_LINE('最低工资: $' || v_min_salary);-- 工资分析IF v_avg_salary > 8000 THENDBMS_OUTPUT.PUT_LINE('工资水平: 较高');ELSIF v_avg_salary > 5000 THENDBMS_OUTPUT.PUT_LINE('工资水平: 中等');ELSEDBMS_OUTPUT.PUT_LINE('工资水平: 偏低');END IF;END employee_count_info;
/-- 执行存储过程
EXEC employee_count_info;

2.2 带参数的存储过程

2.2.1 输入参数 (IN)

-- 创建带输入参数的存储过程
CREATE OR REPLACE PROCEDURE get_employee_info(p_employee_id IN NUMBER
)
ASv_first_name VARCHAR2(50);v_last_name VARCHAR2(50);v_email VARCHAR2(100);v_salary NUMBER;v_hire_date DATE;v_department_name VARCHAR2(50);v_job_title VARCHAR2(50);v_manager_name VARCHAR2(100);
BEGIN-- 查询员工详细信息SELECT e.first_name,e.last_name,e.email,e.salary,e.hire_date,d.department_name,j.job_title,m.first_name || ' ' || m.last_nameINTO v_first_name, v_last_name, v_email, v_salary, v_hire_date,v_department_name, v_job_title, v_manager_nameFROM employees eLEFT JOIN departments d ON e.department_id = d.department_idLEFT JOIN jobs j ON e.job_id = j.job_idLEFT JOIN employees m ON e.manager_id = m.employee_idWHERE e.employee_id = p_employee_id;-- 显示员工信息DBMS_OUTPUT.PUT_LINE('=== 员工信息 ===');DBMS_OUTPUT.PUT_LINE('姓名: ' || v_first_name || ' ' || v_last_name);DBMS_OUTPUT.PUT_LINE('邮箱: ' || v_email);DBMS_OUTPUT.PUT_LINE('工资: $' || v_salary);DBMS_OUTPUT.PUT_LINE('入职日期: ' || TO_CHAR(v_hire_date, 'YYYY-MM-DD'));DBMS_OUTPUT.PUT_LINE('部门: ' || NVL(v_department_name, '未分配'));DBMS_OUTPUT.PUT_LINE('职位: ' || NVL(v_job_title, '未知'));DBMS_OUTPUT.PUT_LINE('经理: ' || NVL(v_manager_name, '无'));-- 计算工作年限DBMS_OUTPUT.PUT_LINE('工作年限: ' || ROUND(MONTHS_BETWEEN(SYSDATE, v_hire_date) / 12, 1) || ' 年');EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('错误: 员工ID ' || p_employee_id || ' 不存在');WHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('发生错误: ' || SQLERRM);
END get_employee_info;
/-- 调用带参数的存储过程
EXEC get_employee_info(100);
EXEC get_employee_info(999); -- 测试不存在的员工

2.2.2 输出参数 (OUT)

-- 创建带输出参数的存储过程
CREATE OR REPLACE PROCEDURE calculate_employee_stats(p_department_id IN NUMBER,p_emp_count OUT NUMBER,p_avg_salary OUT NUMBER,p_total_salary OUT NUMBER,p_max_salary OUT NUMBER,p_min_salary OUT NUMBER
)
AS
BEGINSELECT COUNT(*),ROUND(AVG(salary), 2),SUM(salary),MAX(salary),MIN(salary)INTO p_emp_count, p_avg_salary, p_total_salary, p_max_salary, p_min_salaryFROM employeesWHERE department_id = p_department_id;-- 如果没有找到员工,设置默认值IF p_emp_count = 0 THENp_avg_salary := 0;p_total_salary := 0;p_max_salary := 0;p_min_salary := 0;END IF;EXCEPTIONWHEN OTHERS THENp_emp_count := -1;p_avg_salary := 0;p_total_salary := 0;p_max_salary := 0;p_min_salary := 0;
END calculate_employee_stats;
/-- 调用带输出参数的存储过程
DECLAREv_count NUMBER;v_avg_salary NUMBER;v_total_salary NUMBER;v_max_salary NUMBER;v_min_salary NUMBER;v_dept_id NUMBER := 60; -- IT部门
BEGINcalculate_employee_stats(p_department_id => v_dept_id,p_emp_count => v_count,p_avg_salary => v_avg_salary,p_total_salary => v_total_salary,p_max_salary => v_max_salary,p_min_salary => v_min_salary);DBMS_OUTPUT.PUT_LINE('=== 部门 ' || v_dept_id || ' 统计信息 ===');DBMS_OUTPUT.PUT_LINE('员工数量: ' || v_count);DBMS_OUTPUT.PUT_LINE('平均工资: $' || v_avg_salary);DBMS_OUTPUT.PUT_LINE('工资总额: $' || v_total_salary);DBMS_OUTPUT.PUT_LINE('最高工资: $' || v_max_salary);DBMS_OUTPUT.PUT_LINE('最低工资: $' || v_min_salary);
END;
/

2.2.3 输入输出参数 (IN OUT)

-- 创建带IN OUT参数的存储过程
CREATE OR REPLACE PROCEDURE adjust_salary(p_employee_id IN NUMBER,p_salary_change IN OUT NUMBER,p_result_message OUT VARCHAR2
)
ASv_current_salary NUMBER;v_new_salary NUMBER;v_min_salary NUMBER;v_max_salary NUMBER;v_job_id VARCHAR2(10);
BEGIN-- 获取员工当前信息SELECT salary, job_idINTO v_current_salary, v_job_idFROM employeesWHERE employee_id = p_employee_id;-- 获取职位工资范围SELECT min_salary, max_salaryINTO v_min_salary, v_max_salaryFROM jobsWHERE job_id = v_job_id;-- 如果输入的是百分比(小于1),转换为实际金额IF p_salary_change < 1 AND p_salary_change > -1 THENp_salary_change := v_current_salary * p_salary_change;END IF;-- 计算新工资v_new_salary := v_current_salary + p_salary_change;-- 检查工资范围IF v_new_salary < v_min_salary THENv_new_salary := v_min_salary;p_salary_change := v_new_salary - v_current_salary;p_result_message := '工资已调整到职位最低标准: $' || v_min_salary;ELSIF v_new_salary > v_max_salary THENv_new_salary := v_max_salary;p_salary_change := v_new_salary - v_current_salary;p_result_message := '工资已调整到职位最高标准: $' || v_max_salary;ELSEp_result_message := '工资调整成功: $' || v_current_salary || ' -> $' || v_new_salary;END IF;-- 更新员工工资UPDATE employeesSET salary = v_new_salary,last_updated = SYSDATEWHERE employee_id = p_employee_id;-- 返回实际调整金额p_salary_change := v_new_salary - v_current_salary;EXCEPTIONWHEN NO_DATA_FOUND THENp_result_message := '错误: 员工ID ' || p_employee_id || ' 不存在';p_salary_change := 0;WHEN OTHERS THENp_result_message := '错误: ' || SQLERRM;p_salary_change := 0;ROLLBACK;
END adjust_salary;
/-- 测试IN OUT参数
DECLAREv_salary_change NUMBER := 0.1; -- 10%增长v_message VARCHAR2(200);v_emp_id NUMBER := 107;
BEGINDBMS_OUTPUT.PUT_LINE('调整前工资变化: ' || v_salary_change);adjust_salary(p_employee_id => v_emp_id,p_salary_change => v_salary_change,p_result_message => v_message);DBMS_OUTPUT.PUT_LINE('实际工资变化: $' || v_salary_change);DBMS_OUTPUT.PUT_LINE('结果: ' || v_message);COMMIT;
END;
/

3. 高级存储过程特性

3.1 游标的使用

3.1.1 显式游标

-- 使用显式游标的存储过程
CREATE OR REPLACE PROCEDURE process_department_salaries(p_department_id IN NUMBER,p_increase_percent IN NUMBER DEFAULT 5
)
AS-- 声明游标CURSOR emp_cursor ISSELECT employee_id, first_name, last_name, salaryFROM employeesWHERE department_id = p_department_idORDER BY salary;-- 游标记录类型emp_rec emp_cursor%ROWTYPE;-- 其他变量v_counter NUMBER := 0;v_total_increase NUMBER := 0;v_old_salary NUMBER;v_new_salary NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 部门 ' || p_department_id || ' 工资调整 ===');DBMS_OUTPUT.PUT_LINE('调整比例: ' || p_increase_percent || '%');DBMS_OUTPUT.PUT_LINE('');-- 打开游标OPEN emp_cursor;LOOP-- 提取数据FETCH emp_cursor INTO emp_rec;-- 检查是否还有数据EXIT WHEN emp_cursor%NOTFOUND;-- 处理当前员工v_counter := v_counter + 1;v_old_salary := emp_rec.salary;v_new_salary := ROUND(v_old_salary * (1 + p_increase_percent / 100), 2);-- 更新工资UPDATE employeesSET salary = v_new_salary,last_updated = SYSDATEWHERE employee_id = emp_rec.employee_id;-- 累计增长金额v_total_increase := v_total_increase + (v_new_salary - v_old_salary);-- 显示调整信息DBMS_OUTPUT.PUT_LINE(v_counter || '. ' || emp_rec.first_name || ' ' || emp_rec.last_name || ': $' || v_old_salary || ' -> $' || v_new_salary || ' (+$' || (v_new_salary - v_old_salary) || ')');END LOOP;-- 关闭游标CLOSE emp_cursor;-- 显示汇总信息DBMS_OUTPUT.PUT_LINE('');DBMS_OUTPUT.PUT_LINE('=== 调整汇总 ===');DBMS_OUTPUT.PUT_LINE('处理员工数: ' || v_counter);DBMS_OUTPUT.PUT_LINE('总增长金额: $' || ROUND(v_total_increase, 2));IF v_counter > 0 THENDBMS_OUTPUT.PUT_LINE('平均增长: $' || ROUND(v_total_increase / v_counter, 2));COMMIT;DBMS_OUTPUT.PUT_LINE('工资调整已提交');ELSEDBMS_OUTPUT.PUT_LINE('未找到符合条件的员工');END IF;EXCEPTIONWHEN OTHERS THENIF emp_cursor%ISOPEN THENCLOSE emp_cursor;END IF;ROLLBACK;DBMS_OUTPUT.PUT_LINE('错误: ' || SQLERRM);
END process_department_salaries;
/-- 测试游标存储过程
EXEC process_department_salaries(20, 8); -- IT部门加薪8%

3.1.2 游标FOR循环

-- 使用游标FOR循环的存储过程
CREATE OR REPLACE PROCEDURE generate_employee_report(p_department_id IN NUMBER DEFAULT NULL
)
ASv_total_employees NUMBER := 0;v_total_salary NUMBER := 0;v_dept_name VARCHAR2(50);
BEGIN-- 获取部门名称IF p_department_id IS NOT NULL THENSELECT department_name INTO v_dept_nameFROM departments WHERE department_id = p_department_id;DBMS_OUTPUT.PUT_LINE('=== ' || v_dept_name || ' 部门员工报告 ===');ELSEDBMS_OUTPUT.PUT_LINE('=== 全公司员工报告 ===');END IF;DBMS_OUTPUT.PUT_LINE('');DBMS_OUTPUT.PUT_LINE(RPAD('员工姓名', 20) || RPAD('职位', 15) || RPAD('工资', 10) || RPAD('入职日期', 12) || '工作年限');DBMS_OUTPUT.PUT_LINE(RPAD('-', 70, '-'));-- 游标FOR循环FOR emp_rec IN (SELECT e.first_name || ' ' || e.last_name AS full_name,j.job_title,e.salary,e.hire_date,ROUND(MONTHS_BETWEEN(SYSDATE, e.hire_date) / 12, 1) AS years_serviceFROM employees eLEFT JOIN jobs j ON e.job_id = j.job_idWHERE (p_department_id IS NULL OR e.department_id = p_department_id)ORDER BY e.salary DESC) LOOP-- 处理每个员工记录v_total_employees := v_total_employees + 1;v_total_salary := v_total_salary + emp_rec.salary;DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.full_name, 20) ||RPAD(NVL(emp_rec.job_title, 'N/A'), 15) ||RPAD('$' || emp_rec.salary, 10) ||RPAD(TO_CHAR(emp_rec.hire_date, 'YYYY-MM-DD'), 12) ||emp_rec.years_service || '年');END LOOP;-- 显示汇总信息DBMS_OUTPUT.PUT_LINE(RPAD('-', 70, '-'));DBMS_OUTPUT.PUT_LINE('总员工数: ' || v_total_employees);DBMS_OUTPUT.PUT_LINE('工资总额: $' || v_total_salary);IF v_total_employees > 0 THENDBMS_OUTPUT.PUT_LINE('平均工资: $' || ROUND(v_total_salary / v_total_employees, 2));END IF;EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('未找到指定部门或部门无员工');WHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('生成报告时发生错误: ' || SQLERRM);
END generate_employee_report;
/-- 测试游标FOR循环
EXEC generate_employee_report(60); -- IT部门报告
EXEC generate_employee_report; -- 全公司报告

3.2 异常处理

3.2.1 预定义异常处理

-- 完善的异常处理存储过程
CREATE OR REPLACE PROCEDURE safe_employee_update(p_employee_id IN NUMBER,p_first_name IN VARCHAR2 DEFAULT NULL,p_last_name IN VARCHAR2 DEFAULT NULL,p_email IN VARCHAR2 DEFAULT NULL,p_salary IN NUMBER DEFAULT NULL,p_department_id IN NUMBER DEFAULT NULL
)
ASv_current_email VARCHAR2(100);v_update_count NUMBER := 0;v_sql_stmt VARCHAR2(4000) := 'UPDATE employees SET last_updated = SYSDATE';v_where_clause VARCHAR2(100) := ' WHERE employee_id = :emp_id';
BEGIN-- 验证员工是否存在SELECT email INTO v_current_emailFROM employees WHERE employee_id = p_employee_id;-- 构建动态更新语句IF p_first_name IS NOT NULL THENv_sql_stmt := v_sql_stmt || ', first_name = :first_name';END IF;IF p_last_name IS NOT NULL THENv_sql_stmt := v_sql_stmt || ', last_name = :last_name';END IF;IF p_email IS NOT NULL THEN-- 检查邮箱唯一性SELECT COUNT(*) INTO v_update_countFROM employees WHERE email = p_email AND employee_id != p_employee_id;IF v_update_count > 0 THENRAISE_APPLICATION_ERROR(-20001, '邮箱地址已被其他员工使用');END IF;v_sql_stmt := v_sql_stmt || ', email = :email';END IF;IF p_salary IS NOT NULL THENIF p_salary <= 0 THENRAISE_APPLICATION_ERROR(-20002, '工资必须大于0');END IF;v_sql_stmt := v_sql_stmt || ', salary = :salary';END IF;IF p_department_id IS NOT NULL THEN-- 验证部门是否存在SELECT COUNT(*) INTO v_update_countFROM departments WHERE department_id = p_department_id;IF v_update_count = 0 THENRAISE_APPLICATION_ERROR(-20003, '指定的部门不存在');END IF;v_sql_stmt := v_sql_stmt || ', department_id = :dept_id';END IF;-- 执行更新(这里简化处理,实际应用中可使用动态SQL)UPDATE employees SET first_name = NVL(p_first_name, first_name),last_name = NVL(p_last_name, last_name),email = NVL(p_email, email),salary = NVL(p_salary, salary),department_id = NVL(p_department_id, department_id),last_updated = SYSDATEWHERE employee_id = p_employee_id;v_update_count := SQL%ROWCOUNT;IF v_update_count > 0 THENCOMMIT;DBMS_OUTPUT.PUT_LINE('员工信息更新成功');DBMS_OUTPUT.PUT_LINE('员工ID: ' || p_employee_id);DBMS_OUTPUT.PUT_LINE('更新行数: ' || v_update_count);ELSEDBMS_OUTPUT.PUT_LINE('没有记录被更新');END IF;EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('错误: 员工ID ' || p_employee_id || ' 不存在');WHEN DUP_VAL_ON_INDEX THENROLLBACK;DBMS_OUTPUT.PUT_LINE('错误: 违反唯一性约束,可能是邮箱重复');WHEN VALUE_ERROR THENROLLBACK;DBMS_OUTPUT.PUT_LINE('错误: 数据类型或长度错误');WHEN INVALID_NUMBER THENROLLBACK;DBMS_OUTPUT.PUT_LINE('错误: 无效的数字格式');WHEN OTHERS THENROLLBACK;IF SQLCODE BETWEEN -20999 AND -20000 THEN-- 用户定义的应用错误DBMS_OUTPUT.PUT_LINE('业务错误: ' || SQLERRM);ELSE-- 其他系统错误DBMS_OUTPUT.PUT_LINE('系统错误: ' || SQLCODE || ' - ' || SQLERRM);END IF;
END safe_employee_update;
/-- 测试异常处理
BEGIN-- 正常更新safe_employee_update(p_employee_id => 107,p_first_name => 'Diana',p_salary => 5200);-- 测试各种异常情况safe_employee_update(999, 'Test', 'User'); -- 员工不存在safe_employee_update(107, p_salary => -1000); -- 无效工资safe_employee_update(107, p_department_id => 999); -- 部门不存在
END;
/

3.2.2 用户定义异常

-- 创建带用户定义异常的存储过程
CREATE OR REPLACE PROCEDURE transfer_employee(p_employee_id IN NUMBER,p_new_department_id IN NUMBER,p_effective_date IN DATE DEFAULT SYSDATE
)
AS-- 用户定义异常employee_not_found EXCEPTION;department_not_found EXCEPTION;invalid_transfer_date EXCEPTION;same_department EXCEPTION;employee_is_manager EXCEPTION;-- 变量声明v_current_dept_id NUMBER;v_dept_count NUMBER;v_manager_count NUMBER;v_employee_name VARCHAR2(100);v_old_dept_name VARCHAR2(50);v_new_dept_name VARCHAR2(50);BEGIN-- 验证转移日期IF p_effective_date < TRUNC(SYSDATE) THENRAISE invalid_transfer_date;END IF;-- 获取员工当前信息BEGINSELECT e.department_id, e.first_name || ' ' || e.last_name,d.department_nameINTO v_current_dept_id, v_employee_name, v_old_dept_nameFROM employees eLEFT JOIN departments d ON e.department_id = d.department_idWHERE e.employee_id = p_employee_id;EXCEPTIONWHEN NO_DATA_FOUND THENRAISE employee_not_found;END;-- 检查是否转移到相同部门IF v_current_dept_id = p_new_department_id THENRAISE same_department;END IF;-- 验证新部门是否存在SELECT COUNT(*), MAX(department_name)INTO v_dept_count, v_new_dept_nameFROM departments WHERE department_id = p_new_department_id;IF v_dept_count = 0 THENRAISE department_not_found;END IF;-- 检查员工是否是部门经理SELECT COUNT(*)INTO v_manager_countFROM departmentsWHERE manager_id = p_employee_id;IF v_manager_count > 0 THENRAISE employee_is_manager;END IF;-- 记录转移历史INSERT INTO employee_transfer_history (transfer_id,employee_id,old_department_id,new_department_id,transfer_date,created_by,created_date) VALUES (employee_transfer_seq.NEXTVAL,p_employee_id,v_current_dept_id,p_new_department_id,p_effective_date,USER,SYSDATE);-- 执行转移UPDATE employeesSET department_id = p_new_department_id,last_updated = SYSDATEWHERE employee_id = p_employee_id;COMMIT;-- 输出成功信息DBMS_OUTPUT.PUT_LINE('=== 员工转移成功 ===');DBMS_OUTPUT.PUT_LINE('员工: ' || v_employee_name);DBMS_OUTPUT.PUT_LINE('从: ' || NVL(v_old_dept_name, '未分配部门'));DBMS_OUTPUT.PUT_LINE('到: ' || v_new_dept_name);DBMS_OUTPUT.PUT_LINE('生效日期: ' || TO_CHAR(p_effective_date, 'YYYY-MM-DD'));EXCEPTIONWHEN employee_not_found THENDBMS_OUTPUT.PUT_LINE('错误: 员工ID ' || p_employee_id || ' 不存在');WHEN department_not_found THENDBMS_OUTPUT.PUT_LINE('错误: 目标部门ID ' || p_new_department_id || ' 不存在');WHEN invalid_transfer_date THENDBMS_OUTPUT.PUT_LINE('错误: 转移日期不能早于今天');WHEN same_department THENDBMS_OUTPUT.PUT_LINE('错误: 员工已经在目标部门中');WHEN employee_is_manager THENDBMS_OUTPUT.PUT_LINE('错误: 无法转移部门经理,请先指定新经理');WHEN OTHERS THENROLLBACK;DBMS_OUTPUT.PUT_LINE('转移失败: ' || SQLERRM);
END transfer_employee;
/-- 创建转移历史表(如果不存在)
CREATE TABLE employee_transfer_history (transfer_id NUMBER PRIMARY KEY,employee_id NUMBER,old_department_id NUMBER,new_department_id NUMBER,transfer_date DATE,created_by VARCHAR2(30),created_date DATE
);CREATE SEQUENCE employee_transfer_seq START WITH 1 INCREMENT BY 1;-- 测试用户定义异常
EXEC transfer_employee(107, 20); -- 正常转移
EXEC transfer_employee(999, 20); -- 员工不存在
EXEC transfer_employee(107, 999); -- 部门不存在
EXEC transfer_employee(107, 20); -- 相同部门

4. 存储过程的高级应用

4.1 动态SQL

4.1.1 使用EXECUTE IMMEDIATE

-- 动态SQL存储过程
CREATE OR REPLACE PROCEDURE dynamic_table_stats(p_table_name IN VARCHAR2,p_owner IN VARCHAR2 DEFAULT USER
)
ASv_sql VARCHAR2(4000);v_count NUMBER;v_avg_value NUMBER;v_max_value NUMBER;v_min_value NUMBER;v_column_name VARCHAR2(128);v_data_type VARCHAR2(128);-- 游标:获取数值列CURSOR num_columns_cur ISSELECT column_name, data_typeFROM all_tab_columnsWHERE table_name = UPPER(p_table_name)AND owner = UPPER(p_owner)AND data_type IN ('NUMBER', 'INTEGER', 'FLOAT')ORDER BY column_id;BEGINDBMS_OUTPUT.PUT_LINE('=== 表 ' || p_owner || '.' || p_table_name || ' 统计信息 ===');-- 检查表是否存在v_sql := 'SELECT COUNT(*) FROM ' || p_owner || '.' || p_table_name;BEGINEXECUTE IMMEDIATE v_sql INTO v_count;DBMS_OUTPUT.PUT_LINE('总记录数: ' || v_count);EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('错误: 表不存在或无访问权限');RETURN;END;IF v_count = 0 THENDBMS_OUTPUT.PUT_LINE('表为空,无统计数据');RETURN;END IF;DBMS_OUTPUT.PUT_LINE('');DBMS_OUTPUT.PUT_LINE('数值列统计:');DBMS_OUTPUT.PUT_LINE(RPAD('列名', 20) || RPAD('数据类型', 15) || RPAD('平均值', 12) || RPAD('最大值', 12) || '最小值');DBMS_OUTPUT.PUT_LINE(RPAD('-', 70, '-'));-- 遍历数值列FOR col_rec IN num_columns_cur LOOPBEGIN-- 构建动态SQLv_sql := 'SELECT ROUND(AVG(' || col_rec.column_name || '), 2), ' ||'MAX(' || col_rec.column_name || '), ' ||'MIN(' || col_rec.column_name || ') ' ||'FROM ' || p_owner || '.' || p_table_name ||' WHERE ' || col_rec.column_name || ' IS NOT NULL';EXECUTE IMMEDIATE v_sql INTO v_avg_value, v_max_value, v_min_value;DBMS_OUTPUT.PUT_LINE(RPAD(col_rec.column_name, 20) ||RPAD(col_rec.data_type, 15) ||RPAD(NVL(TO_CHAR(v_avg_value), 'N/A'), 12) ||RPAD(NVL(TO_CHAR(v_max_value), 'N/A'), 12) ||NVL(TO_CHAR(v_min_value), 'N/A'));EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE(RPAD(col_rec.column_name, 20) ||RPAD(col_rec.data_type, 15) ||'计算错误: ' || SUBSTR(SQLERRM, 1, 30));END;END LOOP;EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('执行错误: ' || SQLERRM);
END dynamic_table_stats;
/-- 测试动态SQL
EXEC dynamic_table_stats('EMPLOYEES');
EXEC dynamic_table_stats('DEPARTMENTS');

4.1.2 动态查询构建器

-- 通用查询构建器存储过程
CREATE OR REPLACE PROCEDURE flexible_employee_search(p_department_id IN NUMBER DEFAULT NULL,p_job_id IN VARCHAR2 DEFAULT NULL,p_min_salary IN NUMBER DEFAULT NULL,p_max_salary IN NUMBER DEFAULT NULL,p_hire_date_from IN DATE DEFAULT NULL,p_hire_date_to IN DATE DEFAULT NULL,p_order_by IN VARCHAR2 DEFAULT 'employee_id',p_order_direction IN VARCHAR2 DEFAULT 'ASC'
)
ASv_sql VARCHAR2(4000);v_where_clause VARCHAR2(2000) := '';v_conditions NUMBER := 0;-- 定义REF CURSOR类型TYPE emp_cursor_type IS REF CURSOR;emp_cursor emp_cursor_type;-- 记录类型v_employee_id NUMBER;v_full_name VARCHAR2(100);v_email VARCHAR2(100);v_salary NUMBER;v_hire_date DATE;v_department_name VARCHAR2(50);v_job_title VARCHAR2(50);BEGIN-- 构建基本查询v_sql := 'SELECT e.employee_id, ' ||'e.first_name || '' '' || e.last_name AS full_name, ' ||'e.email, e.salary, e.hire_date, ' ||'d.department_name, j.job_title ' ||'FROM employees e ' ||'LEFT JOIN departments d ON e.department_id = d.department_id ' ||'LEFT JOIN jobs j ON e.job_id = j.job_id';-- 构建WHERE条件IF p_department_id IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.department_id = ' || p_department_id;v_conditions := v_conditions + 1;END IF;IF p_job_id IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.job_id = ''' || p_job_id || '''';v_conditions := v_conditions + 1;END IF;IF p_min_salary IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.salary >= ' || p_min_salary;v_conditions := v_conditions + 1;END IF;IF p_max_salary IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.salary <= ' || p_max_salary;v_conditions := v_conditions + 1;END IF;IF p_hire_date_from IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.hire_date >= DATE ''' || TO_CHAR(p_hire_date_from, 'YYYY-MM-DD') || '''';v_conditions := v_conditions + 1;END IF;IF p_hire_date_to IS NOT NULL THENv_where_clause := v_where_clause || CASE WHEN v_conditions > 0 THEN ' AND ' ELSE ' WHERE ' END ||'e.hire_date <= DATE ''' || TO_CHAR(p_hire_date_to, 'YYYY-MM-DD') || '''';v_conditions := v_conditions + 1;END IF;-- 组合完整SQLv_sql := v_sql || v_where_clause || ' ORDER BY ' || p_order_by || ' ' || p_order_direction;DBMS_OUTPUT.PUT_LINE('=== 员工搜索结果 ===');DBMS_OUTPUT.PUT_LINE('查询SQL: ' || v_sql);DBMS_OUTPUT.PUT_LINE('');DBMS_OUTPUT.PUT_LINE(RPAD('ID', 6) || RPAD('姓名', 20) || RPAD('邮箱', 25) || RPAD('工资', 10) || RPAD('入职日期', 12) || RPAD('部门', 15) || '职位');DBMS_OUTPUT.PUT_LINE(RPAD('-', 100, '-'));-- 执行动态查询OPEN emp_cursor FOR v_sql;LOOPFETCH emp_cursor INTO v_employee_id, v_full_name, v_email, v_salary, v_hire_date, v_department_name, v_job_title;EXIT WHEN emp_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE(RPAD(v_employee_id, 6) ||RPAD(SUBSTR(v_full_name, 1, 19), 20) ||RPAD(SUBSTR(v_email, 1, 24), 25) ||RPAD('$' || v_salary, 10) ||RPAD(TO_CHAR(v_hire_date, 'YYYY-MM-DD'), 12) ||RPAD(SUBSTR(NVL(v_department_name, 'N/A'), 1, 14), 15) ||SUBSTR(NVL(v_job_title, 'N/A'), 1, 20));END LOOP;DBMS_OUTPUT.PUT_LINE(RPAD('-', 100, '-'));DBMS_OUTPUT.PUT_LINE('找到 ' || emp_cursor%ROWCOUNT || ' 条记录');CLOSE emp_cursor;EXCEPTIONWHEN OTHERS THENIF emp_cursor%ISOPEN THENCLOSE emp_cursor;END IF;DBMS_OUTPUT.PUT_LINE('查询执行错误: ' || SQLERRM);
END flexible_employee_search;
/-- 测试动态查询构建器
-- 搜索IT部门,工资在5000-10000之间的员工
EXEC flexible_employee_search(p_department_id => 60, p_min_salary => 5000, p_max_salary => 10000);-- 搜索2005年后入职的员工,按工资降序排列
EXEC flexible_employee_search(p_hire_date_from => DATE '2005-01-01', p_order_by => 'salary', p_order_direction => 'DESC');

4.2 批量处理

4.2.1 BULK COLLECT和FORALL

-- 高性能批量处理存储过程
CREATE OR REPLACE PROCEDURE bulk_salary_adjustment(p_department_id IN NUMBER,p_adjustment_type IN VARCHAR2, -- 'PCT' for percentage, 'AMT' for amountp_adjustment_value IN NUMBER,p_batch_size IN NUMBER DEFAULT 1000
)
AS-- 声明集合类型TYPE emp_id_array IS TABLE OF employees.employee_id%TYPE;TYPE salary_array IS TABLE OF employees.salary%TYPE;TYPE name_array IS TABLE OF VARCHAR2(100);-- 声明集合变量v_emp_ids emp_id_array;v_old_salaries salary_array;v_new_salaries salary_array;v_emp_names name_array;-- 其他变量v_total_processed NUMBER := 0;v_total_adjustment NUMBER := 0;v_batch_count NUMBER := 0;-- 游标声明CURSOR emp_cursor ISSELECT employee_id, salary, first_name || ' ' || last_nameFROM employeesWHERE department_id = p_department_idORDER BY employee_id;BEGINDBMS_OUTPUT.PUT_LINE('=== 批量工资调整开始 ===');DBMS_OUTPUT.PUT_LINE('部门ID: ' || p_department_id);DBMS_OUTPUT.PUT_LINE('调整类型: ' || CASE p_adjustment_type WHEN 'PCT' THEN '百分比' WHEN 'AMT' THEN '固定金额' ELSE '未知' END);DBMS_OUTPUT.PUT_LINE('调整值: ' || p_adjustment_value);DBMS_OUTPUT.PUT_LINE('批次大小: ' || p_batch_size);DBMS_OUTPUT.PUT_LINE('');-- 验证调整类型IF p_adjustment_type NOT IN ('PCT', 'AMT') THENRAISE_APPLICATION_ERROR(-20001, '无效的调整类型,必须是PCT或AMT');END IF;-- 打开游标并批量处理OPEN emp_cursor;LOOP-- 批量获取数据FETCH emp_cursor BULK COLLECT INTO v_emp_ids, v_old_salaries, v_emp_namesLIMIT p_batch_size;-- 退出条件EXIT WHEN v_emp_ids.COUNT = 0;v_batch_count := v_batch_count + 1;DBMS_OUTPUT.PUT_LINE('处理第 ' || v_batch_count || ' 批,记录数: ' || v_emp_ids.COUNT);-- 初始化新工资数组v_new_salaries := salary_array();v_new_salaries.EXTEND(v_emp_ids.COUNT);-- 计算新工资FOR i IN 1..v_emp_ids.COUNT LOOPIF p_adjustment_type = 'PCT' THENv_new_salaries(i) := ROUND(v_old_salaries(i) * (1 + p_adjustment_value / 100), 2);ELSE -- AMTv_new_salaries(i) := v_old_salaries(i) + p_adjustment_value;END IF;-- 确保工资不为负数IF v_new_salaries(i) < 0 THENv_new_salaries(i) := 0;END IF;v_total_adjustment := v_total_adjustment + (v_new_salaries(i) - v_old_salaries(i));END LOOP;-- 批量更新FORALL i IN 1..v_emp_ids.COUNTUPDATE employeesSET salary = v_new_salaries(i),last_updated = SYSDATEWHERE employee_id = v_emp_ids(i);-- 记录处理的员工信息FOR i IN 1..v_emp_ids.COUNT LOOPDBMS_OUTPUT.PUT_LINE('  ' || v_emp_names(i) || ': $' || v_old_salaries(i) || ' -> $' || v_new_salaries(i) || ' (变化: $' || (v_new_salaries(i) - v_old_salaries(i)) || ')');END LOOP;v_total_processed := v_total_processed + v_emp_ids.COUNT;-- 提交当前批次COMMIT;DBMS_OUTPUT.PUT_LINE('第 ' || v_batch_count || ' 批处理完成并提交');DBMS_OUTPUT.PUT_LINE('');END LOOP;CLOSE emp_cursor;-- 显示汇总信息DBMS_OUTPUT.PUT_LINE('=== 批量调整完成 ===');DBMS_OUTPUT.PUT_LINE('总处理员工数: ' || v_total_processed);DBMS_OUTPUT.PUT_LINE('总调整金额: $' || ROUND(v_total_adjustment, 2));DBMS_OUTPUT.PUT_LINE('处理批次数: ' || v_batch_count);IF v_total_processed > 0 THENDBMS_OUTPUT.PUT_LINE('平均调整: $' || ROUND(v_total_adjustment / v_total_processed, 2));END IF;EXCEPTIONWHEN OTHERS THENIF emp_cursor%ISOPEN THENCLOSE emp_cursor;END IF;ROLLBACK;DBMS_OUTPUT.PUT_LINE('批量处理失败: ' || SQLERRM);DBMS_OUTPUT.PUT_LINE('已回滚所有更改');
END bulk_salary_adjustment;
/-- 测试批量处理
EXEC bulk_salary_adjustment(60, 'PCT', 10, 5); -- IT部门加薪10%,每批5人
EXEC bulk_salary_adjustment(20, 'AMT', 500, 3); -- 市场部每人加薪$500,每批3人

4.2.2 错误处理的批量操作

-- 带错误处理的批量数据导入存储过程
CREATE OR REPLACE PROCEDURE bulk_import_employees(p_batch_size IN NUMBER DEFAULT 100
)
AS-- 集合类型定义TYPE emp_record_type IS RECORD (employee_id NUMBER,first_name VARCHAR2(50),last_name VARCHAR2(50),email VARCHAR2(100),hire_date DATE,job_id VARCHAR2(10),salary NUMBER,department_id NUMBER);TYPE emp_array_type IS TABLE OF emp_record_type;v_employees emp_array_type;-- 错误处理相关TYPE error_array_type IS TABLE OF NUMBER;v_error_indexes error_array_type;v_success_count NUMBER := 0;v_error_count NUMBER := 0;v_total_processed NUMBER := 0;-- 游标:从临时导入表读取数据CURSOR import_cursor ISSELECT employee_id, first_name, last_name, email, hire_date,job_id, salary, department_idFROM temp_employee_importWHERE processed_flag IS NULLORDER BY employee_id;BEGINDBMS_OUTPUT.PUT_LINE('=== 开始批量导入员工数据 ===');DBMS_OUTPUT.PUT_LINE('批次大小: ' || p_batch_size);DBMS_OUTPUT.PUT_LINE('');-- 创建临时导入表(如果不存在)BEGINEXECUTE IMMEDIATE 'CREATE TABLE temp_employee_import AS SELECT * FROM employees WHERE 1=0';EXECUTE IMMEDIATE 'ALTER TABLE temp_employee_import ADD processed_flag VARCHAR2(1)';EXECUTE IMMEDIATE 'ALTER TABLE temp_employee_import ADD error_message VARCHAR2(500)';DBMS_OUTPUT.PUT_LINE('创建临时导入表');EXCEPTIONWHEN OTHERS THENIF SQLCODE != -955 THEN -- 表已存在RAISE;END IF;END;OPEN import_cursor;LOOP-- 批量获取数据FETCH import_cursor BULK COLLECT INTO v_employees LIMIT p_batch_size;EXIT WHEN v_employees.COUNT = 0;DBMS_OUTPUT.PUT_LINE('处理批次,记录数: ' || v_employees.COUNT);-- 使用FORALL进行批量插入,并收集错误BEGINFORALL i IN 1..v_employees.COUNT SAVE EXCEPTIONSINSERT INTO employees (employee_id, first_name, last_name, email, hire_date,job_id, salary, department_id, last_updated) VALUES (v_employees(i).employee_id,v_employees(i).first_name,v_employees(i).last_name,v_employees(i).email,v_employees(i).hire_date,v_employees(i).job_id,v_employees(i).salary,v_employees(i).department_id,SYSDATE);-- 所有记录都成功插入v_success_count := v_success_count + v_employees.COUNT;-- 标记为已处理FORALL i IN 1..v_employees.COUNTUPDATE temp_employee_importSET processed_flag = 'S'WHERE employee_id = v_employees(i).employee_id;EXCEPTIONWHEN OTHERS THEN-- 处理批量操作中的错误IF SQLCODE = -24381 THEN -- FORALL with SAVE EXCEPTIONSv_error_count := v_error_count + SQL%BULK_EXCEPTIONS.COUNT;v_success_count := v_success_count + (v_employees.COUNT - SQL%BULK_EXCEPTIONS.COUNT);DBMS_OUTPUT.PUT_LINE('批次中有 ' || SQL%BULK_EXCEPTIONS.COUNT || ' 个错误');-- 处理每个错误FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOPDECLAREv_error_index NUMBER := SQL%BULK_EXCEPTIONS(i).ERROR_INDEX;v_error_code NUMBER := SQL%BULK_EXCEPTIONS(i).ERROR_CODE;v_error_msg VARCHAR2(500) := SQLERRM(-v_error_code);BEGINDBMS_OUTPUT.PUT_LINE('  错误 ' || i || ': 员工ID ' || v_employees(v_error_index).employee_id ||' - ' || v_error_msg);-- 更新错误信息UPDATE temp_employee_importSET processed_flag = 'E',error_message = v_error_msgWHERE employee_id = v_employees(v_error_index).employee_id;END;END LOOP;-- 标记成功的记录FOR i IN 1..v_employees.COUNT LOOPDECLAREv_is_error BOOLEAN := FALSE;BEGIN-- 检查是否是错误记录FOR j IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOPIF SQL%BULK_EXCEPTIONS(j).ERROR_INDEX = i THENv_is_error := TRUE;EXIT;END IF;END LOOP;-- 如果不是错误记录,标记为成功IF NOT v_is_error THENUPDATE temp_employee_importSET processed_flag = 'S'WHERE employee_id = v_employees(i).employee_id;END IF;END;END LOOP;ELSE-- 其他错误RAISE;END IF;END;v_total_processed := v_total_processed + v_employees.COUNT;COMMIT;DBMS_OUTPUT.PUT_LINE('批次处理完成');DBMS_OUTPUT.PUT_LINE('');END LOOP;CLOSE import_cursor;-- 显示最终统计DBMS_OUTPUT.PUT_LINE('=== 导入完成 ===');DBMS_OUTPUT.PUT_LINE('总处理记录: ' || v_total_processed);DBMS_OUTPUT.PUT_LINE('成功导入: ' || v_success_count);DBMS_OUTPUT.PUT_LINE('失败记录: ' || v_error_count);DBMS_OUTPUT.PUT_LINE('成功率: ' || ROUND(v_success_count / v_total_processed * 100, 2) || '%');EXCEPTIONWHEN OTHERS THENIF import_cursor%ISOPEN THENCLOSE import_cursor;END IF;ROLLBACK;DBMS_OUTPUT.PUT_LINE('导入过程发生严重错误: ' || SQLERRM);
END bulk_import_employees;
/-- 创建错误报告存储过程
CREATE OR REPLACE PROCEDURE show_import_errors
AS
BEGINDBMS_OUTPUT.PUT_LINE('=== 导入错误报告 ===');FOR rec IN (SELECT employee_id, first_name, last_name, error_messageFROM temp_employee_importWHERE processed_flag = 'E'ORDER BY employee_id) LOOPDBMS_OUTPUT.PUT_LINE('员工ID: ' || rec.employee_id ||', 姓名: ' || rec.first_name || ' ' || rec.last_name ||', 错误: ' || rec.error_message);END LOOP;
END;
/

5. 存储过程的调试与优化

5.1 调试技术

5.1.1 使用DBMS_OUTPUT进行调试

-- 带调试信息的存储过程
CREATE OR REPLACE PROCEDURE debug_salary_calculation(p_employee_id IN NUMBER,p_debug_mode IN BOOLEAN DEFAULT FALSE
)
ASv_base_salary NUMBER;v_bonus_pct NUMBER;v_commission_pct NUMBER;v_total_compensation NUMBER;v_tax_rate NUMBER := 0.25; -- 25%税率v_net_salary NUMBER;PROCEDURE debug_print(p_message VARCHAR2) ISBEGINIF p_debug_mode THENDBMS_OUTPUT.PUT_LINE('[DEBUG] ' || TO_CHAR(SYSDATE, 'HH24:MI:SS') || ' - ' || p_message);END IF;END debug_print;BEGINdebug_print('开始计算员工 ' || p_employee_id || ' 的薪资');-- 获取基本工资SELECT salary, NVL(commission_pct, 0)INTO v_base_salary, v_commission_pctFROM employeesWHERE employee_id = p_employee_id;debug_print('基本工资: $' || v_base_salary);debug_print('佣金比例: ' || (v_commission_pct * 100) || '%');-- 计算奖金比例(基于工作年限)SELECT CASE WHEN MONTHS_BETWEEN(SYSDATE, hire_date) >= 60 THEN 0.15  -- 5年以上15%WHEN MONTHS_BETWEEN(SYSDATE, hire_date) >= 36 THEN 0.10  -- 3年以上10%WHEN MONTHS_BETWEEN(SYSDATE, hire_date) >= 12 THEN 0.05  -- 1年以上5%ELSE 0ENDINTO v_bonus_pctFROM employeesWHERE employee_id = p_employee_id;debug_print('奖金比例: ' || (v_bonus_pct * 100) || '%');-- 计算总薪酬v_total_compensation := v_base_salary * (1 + v_bonus_pct + v_commission_pct);debug_print('税前总薪酬: $' || ROUND(v_total_compensation, 2));-- 计算税后薪资v_net_salary := v_total_compensation * (1 - v_tax_rate);debug_print('税后薪资: $' || ROUND(v_net_salary, 2));debug_print('税额: $' || ROUND(v_total_compensation - v_net_salary, 2));-- 更新员工薪资信息(假设有扩展表)BEGINUPDATE employee_compensationSET base_salary = v_base_salary,bonus_amount = v_base_salary * v_bonus_pct,commission_amount = v_base_salary * v_commission_pct,gross_salary = v_total_compensation,tax_amount = v_total_compensation - v_net_salary,net_salary = v_net_salary,calculation_date = SYSDATEWHERE employee_id = p_employee_id;IF SQL%ROWCOUNT = 0 THENdebug_print('员工薪资记录不存在,插入新记录');INSERT INTO employee_compensation (employee_id, base_salary, bonus_amount, commission_amount,gross_salary, tax_amount, net_salary, calculation_date) VALUES (p_employee_id, v_base_salary, v_base_salary * v_bonus_pct,v_base_salary * v_commission_pct, v_total_compensation,v_total_compensation - v_net_salary, v_net_salary, SYSDATE);END IF;debug_print('薪资记录更新成功');EXCEPTIONWHEN OTHERS THENdebug_print('更新薪资记录时出错: ' || SQLERRM);-- 创建表(如果不存在)EXECUTE IMMEDIATE 'CREATE TABLE employee_compensation (employee_id NUMBER PRIMARY KEY,base_salary NUMBER,bonus_amount NUMBER,commission_amount NUMBER,gross_salary NUMBER,tax_amount NUMBER,net_salary NUMBER,calculation_date DATE)';debug_print('创建薪资表并重试');INSERT INTO employee_compensation (employee_id, base_salary, bonus_amount, commission_amount,gross_salary, tax_amount, net_salary, calculation_date) VALUES (p_employee_id, v_base_salary, v_base_salary * v_bonus_pct,v_base_salary * v_commission_pct, v_total_compensation,v_total_compensation - v_net_salary, v_net_salary, SYSDATE);END;COMMIT;debug_print('薪资计算完成');-- 输出结果DBMS_OUTPUT.PUT_LINE('=== 员工 ' || p_employee_id || ' 薪资计算结果 ===');DBMS_OUTPUT.PUT_LINE('基本工资: $' || v_base_salary);DBMS_OUTPUT.PUT_LINE('奖金: $' || ROUND(v_base_salary * v_bonus_pct, 2));DBMS_OUTPUT.PUT_LINE('佣金: $' || ROUND(v_base_salary * v_commission_pct, 2));DBMS_OUTPUT.PUT_LINE('税前总额: $' || ROUND(v_total_compensation, 2));DBMS_OUTPUT.PUT_LINE('税后薪资: $' || ROUND(v_net_salary, 2));EXCEPTIONWHEN NO_DATA_FOUND THENdebug_print('员工不存在: ' || p_employee_id);DBMS_OUTPUT.PUT_LINE('错误: 员工ID ' || p_employee_id || ' 不存在');WHEN OTHERS THENdebug_print('发生错误: ' || SQLERRM);ROLLBACK;DBMS_OUTPUT.PUT_LINE('计算失败: ' || SQLERRM);
END debug_salary_calculation;
/-- 测试调试功能
EXEC debug_salary_calculation(100, TRUE);  -- 开启调试模式
EXEC debug_salary_calculation(101, FALSE); -- 关闭调试模式

5.1.2 性能监控和分析

-- 性能监控存储过程
CREATE OR REPLACE PROCEDURE performance_test_procedure
ASv_start_time TIMESTAMP;v_end_time TIMESTAMP;v_elapsed_time INTERVAL DAY TO SECOND;v_cpu_time NUMBER;v_logical_reads NUMBER;v_physical_reads NUMBER;-- 获取会话统计信息FUNCTION get_session_stat(p_stat_name VARCHAR2) RETURN NUMBER ISv_value NUMBER;BEGINSELECT value INTO v_valueFROM v$mystat m, v$statname nWHERE m.statistic# = n.statistic#AND n.name = p_stat_name;RETURN v_value;EXCEPTIONWHEN NO_DATA_FOUND THENRETURN 0;END;v_start_cpu NUMBER;v_start_logical_reads NUMBER;v_start_physical_reads NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 性能测试开始 ===');-- 记录开始时间和统计信息v_start_time := SYSTIMESTAMP;v_start_cpu := get_session_stat('CPU used by this session');v_start_logical_reads := get_session_stat('session logical reads');v_start_physical_reads := get_session_stat('physical reads');-- 执行测试操作DBMS_OUTPUT.PUT_LINE('执行测试操作...');-- 模拟复杂查询FOR i IN 1..1000 LOOPDECLAREv_count NUMBER;v_avg_salary NUMBER;BEGINSELECT COUNT(*), AVG(salary)INTO v_count, v_avg_salaryFROM employees eJOIN departments d ON e.department_id = d.department_idWHERE e.salary > 5000;END;END LOOP;-- 记录结束时间和统计信息v_end_time := SYSTIMESTAMP;v_cpu_time := get_session_stat('CPU used by this session') - v_start_cpu;v_logical_reads := get_session_stat('session logical reads') - v_start_logical_reads;v_physical_reads := get_session_stat('physical reads') - v_start_physical_reads;-- 计算执行时间v_elapsed_time := v_end_time - v_start_time;-- 输出性能统计DBMS_OUTPUT.PUT_LINE('=== 性能统计结果 ===');DBMS_OUTPUT.PUT_LINE('执行时间: ' || EXTRACT(SECOND FROM v_elapsed_time) + EXTRACT(MINUTE FROM v_elapsed_time) * 60 + EXTRACT(HOUR FROM v_elapsed_time) * 3600 || ' 秒');DBMS_OUTPUT.PUT_LINE('CPU时间: ' || v_cpu_time || ' 厘秒');DBMS_OUTPUT.PUT_LINE('逻辑读: ' || v_logical_reads || ' 块');DBMS_OUTPUT.PUT_LINE('物理读: ' || v_physical_reads || ' 块');DBMS_OUTPUT.PUT_LINE('缓存命中率: ' || ROUND((v_logical_reads - v_physical_reads) / v_logical_reads * 100, 2) || '%');END performance_test_procedure;
/-- 执行性能测试
EXEC performance_test_procedure;

5.2 存储过程优化

5.2.1 SQL优化技巧

-- 优化前后对比的存储过程
CREATE OR REPLACE PROCEDURE optimized_employee_analysis(p_use_optimized IN BOOLEAN DEFAULT TRUE
)
ASv_start_time TIMESTAMP;v_end_time TIMESTAMP;v_count NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 员工分析性能对比 ===');IF p_use_optimized THENDBMS_OUTPUT.PUT_LINE('使用优化版本...');v_start_time := SYSTIMESTAMP;-- 优化版本:使用分析函数和单次查询FOR rec IN (SELECT department_name,employee_count,avg_salary,max_salary,min_salary,salary_rankFROM (SELECT d.department_name,COUNT(e.employee_id) OVER (PARTITION BY d.department_id) AS employee_count,ROUND(AVG(e.salary) OVER (PARTITION BY d.department_id), 2) AS avg_salary,MAX(e.salary) OVER (PARTITION BY d.department_id) AS max_salary,MIN(e.salary) OVER (PARTITION BY d.department_id) AS min_salary,RANK() OVER (ORDER BY AVG(e.salary) OVER (PARTITION BY d.department_id) DESC) AS salary_rank,ROW_NUMBER() OVER (PARTITION BY d.department_id ORDER BY e.employee_id) AS rnFROM departments dLEFT JOIN employees e ON d.department_id = e.department_id)WHERE rn = 1 AND employee_count > 0ORDER BY salary_rank) LOOPv_count := v_count + 1;END LOOP;ELSEDBMS_OUTPUT.PUT_LINE('使用未优化版本...');v_start_time := SYSTIMESTAMP;-- 未优化版本:多次查询FOR dept_rec IN (SELECT department_id, department_name FROM departments) LOOPDECLAREv_emp_count NUMBER;v_avg_salary NUMBER;v_max_salary NUMBER;v_min_salary NUMBER;BEGIN-- 每个部门都要执行一次查询SELECT COUNT(*), NVL(ROUND(AVG(salary), 2), 0),NVL(MAX(salary), 0),NVL(MIN(salary), 0)INTO v_emp_count, v_avg_salary, v_max_salary, v_min_salaryFROM employeesWHERE department_id = dept_rec.department_id;IF v_emp_count > 0 THENv_count := v_count + 1;END IF;END;END LOOP;END IF;v_end_time := SYSTIMESTAMP;DBMS_OUTPUT.PUT_LINE('处理部门数: ' || v_count);DBMS_OUTPUT.PUT_LINE('执行时间: ' || ROUND(EXTRACT(SECOND FROM (v_end_time - v_start_time)) * 1000, 2) || ' 毫秒');END optimized_employee_analysis;
/-- 性能对比测试
EXEC optimized_employee_analysis(FALSE); -- 未优化版本
EXEC optimized_employee_analysis(TRUE);  -- 优化版本

5.2.2 内存和资源优化

-- 资源优化的存储过程
CREATE OR REPLACE PROCEDURE resource_optimized_batch_process(p_table_name IN VARCHAR2,p_batch_size IN NUMBER DEFAULT 1000
)
ASTYPE id_array_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;v_ids id_array_type;v_sql VARCHAR2(4000);v_cursor_id INTEGER;v_rows_processed NUMBER;v_total_processed NUMBER := 0;v_batch_count NUMBER := 0;-- 使用REF CURSOR减少内存占用TYPE ref_cursor_type IS REF CURSOR;v_cursor ref_cursor_type;v_id NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 资源优化批处理 ===');DBMS_OUTPUT.PUT_LINE('表名: ' || p_table_name);DBMS_OUTPUT.PUT_LINE('批次大小: ' || p_batch_size);-- 构建查询SQLv_sql := 'SELECT id FROM ' || p_table_name || ' WHERE processed_flag IS NULL ORDER BY id';OPEN v_cursor FOR v_sql;LOOP-- 清空数组v_ids.DELETE;v_rows_processed := 0;-- 批量获取IDFOR i IN 1..p_batch_size LOOPFETCH v_cursor INTO v_id;EXIT WHEN v_cursor%NOTFOUND;v_ids(i) := v_id;v_rows_processed := v_rows_processed + 1;END LOOP;EXIT WHEN v_rows_processed = 0;v_batch_count := v_batch_count + 1;DBMS_OUTPUT.PUT_LINE('处理第 ' || v_batch_count || ' 批,记录数: ' || v_rows_processed);-- 批量处理BEGINFORALL i IN 1..v_rows_processedEXECUTE IMMEDIATE 'UPDATE ' || p_table_name || ' SET processed_flag = ''Y'', processed_date = SYSDATE WHERE id = :1'USING v_ids(i);v_total_processed := v_total_processed + v_rows_processed;-- 定期提交以释放锁和日志空间COMMIT;-- 可选:在批次间暂停以减少系统负载IF MOD(v_batch_count, 10) = 0 THENDBMS_OUTPUT.PUT_LINE('已处理 ' || v_batch_count || ' 批,暂停1秒...');DBMS_LOCK.SLEEP(1);END IF;EXCEPTIONWHEN OTHERS THENROLLBACK;DBMS_OUTPUT.PUT_LINE('批次 ' || v_batch_count || ' 处理失败: ' || SQLERRM);-- 继续处理下一批END;END LOOP;CLOSE v_cursor;DBMS_OUTPUT.PUT_LINE('=== 处理完成 ===');DBMS_OUTPUT.PUT_LINE('总处理记录: ' || v_total_processed);DBMS_OUTPUT.PUT_LINE('处理批次: ' || v_batch_count);EXCEPTIONWHEN OTHERS THENIF v_cursor%ISOPEN THENCLOSE v_cursor;END IF;ROLLBACK;DBMS_OUTPUT.PUT_LINE('处理过程发生错误: ' || SQLERRM);
END resource_optimized_batch_process;
/

6. 存储过程的安全性

6.1 权限管理

6.1.1 存储过程权限控制

-- 创建带权限检查的存储过程
CREATE OR REPLACE PROCEDURE secure_salary_update(p_employee_id IN NUMBER,p_new_salary IN NUMBER,p_reason IN VARCHAR2
)
ASv_current_user VARCHAR2(30);v_user_role VARCHAR2(30);v_current_salary NUMBER;v_max_allowed_salary NUMBER;v_is_authorized BOOLEAN := FALSE;-- 权限检查函数FUNCTION check_user_permission(p_operation VARCHAR2) RETURN BOOLEAN ISv_count NUMBER;BEGIN-- 检查用户是否有特定权限SELECT COUNT(*)INTO v_countFROM user_role_privsWHERE granted_role IN ('HR_MANAGER', 'SALARY_ADMIN')OR granted_role = 'DBA';RETURN v_count > 0;END;-- 审计日志记录PROCEDURE log_salary_change(p_emp_id NUMBER,p_old_salary NUMBER,p_new_salary NUMBER,p_changed_by VARCHAR2,p_reason VARCHAR2,p_status VARCHAR2) ISPRAGMA AUTONOMOUS_TRANSACTION;BEGININSERT INTO salary_audit_log (log_id, employee_id, old_salary, new_salary,changed_by, change_reason, change_status,change_date) VALUES (salary_audit_seq.NEXTVAL, p_emp_id, p_old_salary, p_new_salary,p_changed_by, p_reason, p_status, SYSDATE);COMMIT;END;BEGIN-- 获取当前用户信息v_current_user := USER;DBMS_OUTPUT.PUT_LINE('=== 安全工资更新 ===');DBMS_OUTPUT.PUT_LINE('操作用户: ' || v_current_user);DBMS_OUTPUT.PUT_LINE('员工ID: ' || p_employee_id);DBMS_OUTPUT.PUT_LINE('新工资: $' || p_new_salary);-- 权限检查IF NOT check_user_permission('SALARY_UPDATE') THENlog_salary_change(p_employee_id, NULL, p_new_salary, v_current_user, p_reason, 'PERMISSION_DENIED');RAISE_APPLICATION_ERROR(-20001, '权限不足:用户无工资更新权限');END IF;-- 获取当前工资SELECT salary INTO v_current_salaryFROM employeesWHERE employee_id = p_employee_id;-- 业务规则检查IF p_new_salary <= 0 THENlog_salary_change(p_employee_id, v_current_salary, p_new_salary, v_current_user, p_reason, 'INVALID_AMOUNT');RAISE_APPLICATION_ERROR(-20002, '工资金额必须大于0');END IF;-- 检查工资增长限制(不能超过50%)IF p_new_salary > v_current_salary * 1.5 THENlog_salary_change(p_employee_id, v_current_salary, p_new_salary, v_current_user, p_reason, 'EXCESSIVE_INCREASE');RAISE_APPLICATION_ERROR(-20003, '工资增长不能超过50%');END IF;-- 检查职位工资上限SELECT j.max_salary INTO v_max_allowed_salaryFROM employees eJOIN jobs j ON e.job_id = j.job_idWHERE e.employee_id = p_employee_id;IF p_new_salary > v_max_allowed_salary THENlog_salary_change(p_employee_id, v_current_salary, p_new_salary, v_current_user, p_reason, 'EXCEEDS_JOB_LIMIT');RAISE_APPLICATION_ERROR(-20004, '工资超过职位上限: $' || v_max_allowed_salary);END IF;-- 执行更新UPDATE employeesSET salary = p_new_salary,last_updated = SYSDATEWHERE employee_id = p_employee_id;-- 记录成功的更改log_salary_change(p_employee_id, v_current_salary, p_new_salary, v_current_user, p_reason, 'SUCCESS');COMMIT;DBMS_OUTPUT.PUT_LINE('工资更新成功');DBMS_OUTPUT.PUT_LINE('原工资: $' || v_current_salary);DBMS_OUTPUT.PUT_LINE('新工资: $' || p_new_salary);DBMS_OUTPUT.PUT_LINE('变化: $' || (p_new_salary - v_current_salary));EXCEPTIONWHEN NO_DATA_FOUND THENlog_salary_change(p_employee_id, NULL, p_new_salary, v_current_user, p_reason, 'EMPLOYEE_NOT_FOUND');DBMS_OUTPUT.PUT_LINE('错误: 员工不存在');WHEN OTHERS THENROLLBACK;DBMS_OUTPUT.PUT_LINE('更新失败: ' || SQLERRM);
END secure_salary_update;
/-- 创建审计日志表
CREATE TABLE salary_audit_log (log_id NUMBER PRIMARY KEY,employee_id NUMBER,old_salary NUMBER,new_salary NUMBER,changed_by VARCHAR2(30),change_reason VARCHAR2(500),change_status VARCHAR2(50),change_date DATE
);CREATE SEQUENCE salary_audit_seq START WITH 1 INCREMENT BY 1;-- 创建审计查询存储过程
CREATE OR REPLACE PROCEDURE show_salary_audit(p_employee_id IN NUMBER DEFAULT NULL,p_days_back IN NUMBER DEFAULT 30
)
AS
BEGINDBMS_OUTPUT.PUT_LINE('=== 工资变更审计报告 ===');DBMS_OUTPUT.PUT_LINE('查询范围: 最近 ' || p_days_back || ' 天');IF p_employee_id IS NOT NULL THENDBMS_OUTPUT.PUT_LINE('员工ID: ' || p_employee_id);END IF;DBMS_OUTPUT.PUT_LINE('');FOR rec IN (SELECT l.log_id, l.employee_id, e.first_name || ' ' || e.last_name AS employee_name,l.old_salary, l.new_salary, l.changed_by, l.change_reason,l.change_status, l.change_dateFROM salary_audit_log lLEFT JOIN employees e ON l.employee_id = e.employee_idWHERE (p_employee_id IS NULL OR l.employee_id = p_employee_id)AND l.change_date >= SYSDATE - p_days_backORDER BY l.change_date DESC) LOOPDBMS_OUTPUT.PUT_LINE('记录ID: ' || rec.log_id);DBMS_OUTPUT.PUT_LINE('员工: ' || NVL(rec.employee_name, '未知') || ' (ID: ' || rec.employee_id || ')');DBMS_OUTPUT.PUT_LINE('工资变化: $' || NVL(rec.old_salary, 0) || ' -> $' || rec.new_salary);DBMS_OUTPUT.PUT_LINE('操作人: ' || rec.changed_by);DBMS_OUTPUT.PUT_LINE('状态: ' || rec.change_status);DBMS_OUTPUT.PUT_LINE('时间: ' || TO_CHAR(rec.change_date, 'YYYY-MM-DD HH24:MI:SS'));DBMS_OUTPUT.PUT_LINE('原因: ' || NVL(rec.change_reason, '未提供'));DBMS_OUTPUT.PUT_LINE('---');END LOOP;
END show_salary_audit;
/-- 测试安全存储过程
EXEC secure_salary_update(100, 25000, '年度调薪');
EXEC show_salary_audit(100, 7); -- 查看员工100最近7天的工资变更

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

http://www.xdnf.cn/news/871399.html

相关文章:

  • 纹理压缩格式优化
  • OpenCV 自带颜色表实现各种滤镜
  • 【Netty源码分析总结】
  • 使用 Unstructured 开源库快速入门指南
  • 机器学习:聚类算法
  • Python爬虫:trafilatura 的详细使用(快速提取正文和评论以及结构,转换为 TXT、CSV 和 XML)
  • Day44打卡 @浙大疏锦行
  • 01-Redis介绍与安装
  • pyqt5 安装失败
  • 查看服务应用是否有跑起来命令
  • 机器学习算法分类
  • 3D旋转动态爱心 - Python创意代码
  • MySQL ACID 面试深度解析:原理、实现与面试实战
  • Pytest+Selenium UI自动化测试实战实例
  • MCP:AI应用的通用接口,如何重塑大模型与外部系统的连接?
  • 小米又开源了,一个多模态大模型 + 一个生不逢时的推理大模型
  • CppCon 2015 学习:Bridging Languages Cross-Platform
  • 循环链表与循环队列的区分与对比
  • 防火墙iptables项目实战
  • Java 二维码
  • ROS2性能狂飙:C++11移动语义‘偷梁换柱’实战
  • CSP严格模式返回不存在的爬虫相关文件
  • C#和C++在编译过程中的文件区分
  • 树莓派上遇到插入耳机后显示“无输入设备”问题
  • 格恩朗椭圆齿轮流量计 精准计量 赋能工业
  • 探索花语的奥秘:全新花语网站上线啦!
  • Elasticsearch中的地理空间(Geo)数据类型介绍
  • PostgreSQL配置文件修改及启用方法
  • ubutu修改网关
  • 将多个分段btsnoop文件合并为一个