PL/SQL
一、Function
语法:
CREATE [OR REPLACE] FUNCTION FunctionName(param1 [IN|OUT|IN OUT] NUMBER,param1 [IN|OUT|IN OUT] VARCHAR2)
RETURN VARCHAR2
IS
--声明变量、游标、常量等
--基本变量声明
v_employee_name VARCHAR2(100); --字符串变量
v_salary NUMBER(10,2); --数字变量
v_hire_date DATE; --日期变量
v_is_active BOOLEAN := TRUE; --布尔变量
v_department_id NUMBER := 10; --初始值数字变量
--复杂变量声明--基于表字段类型的变量
v_emp_record employees%ROWTYPE; --记录类型,匹配employee表结构--基于表字段的变量
v_dept_name departments.department_name%TYPE;--声明一个变量 v_dept_name,其数据类型与 departments 表中 department_name 列的数据类型完全一致。--基本游标的声明
CURSOR c_employees ISSELECT employee_id,last_name,salaryFROM employeesWHERE department_id = 10;--在 PL/SQL 中逐行处理 employees 表中 department_id = 10 的员工数据。
--带参数的游标
CURSOR c_dept_employees(p_dept_id NUMBER) ISSELECT employee_id,last_nameFROM employeesWHERE department_id = dept_id;--通过参数 p_dept_id 可以灵活查询不同部门的员工,无需为每个部门编写单独的游标。--弱类型游标变量 (Weakly Typed REF CURSOR)
--不指定返回结构:可以关联到任何 SELECT 语句
--灵活性高:同一个游标变量可以在不同时间指向不同的查询
--类型安全低:需要在运行时才知道返回的数据结构TYPE t_emp_cursor IS REF CURSOR;cv_emp t_emp_cursor;--强类型游标变量 (Strongly Typed REF CURSOR)
--指定返回类型:必须返回与定义匹配的结构
--类型安全高:编译时检查,避免运行时错误
--可维护性好:明确知道游标返回的数据结构TYPE t_dept_cursor IS REF CURSOR RETURN departments%ROWTYPE;cv_dept t_dept_cursor;--基本常量
c_company_name CONSTANT VARCHAR2(100) :='Oracle Corporation';
c_pi CONSTANT NUMBER :=3.1415926;
c_tax_rate CONSTANT NUMBER(5,4) :=0.0825;-- 8.25%的税率 第一个数字(5):表示这个数的总位数(包括整数位和小数位)第二个数字(4):表示小数部分的位数--基于函数的常量
c_user CONSTANT VARCHAR2(30) :=USER;
c_current_date CONSTANT DATE := SYSDATE;
- 命名规范:
- 变量:v_前缀 (如 v_salary)
- 常量:c_前缀 (如 c_pi)
- 游标:c_前缀 (如 c_employees)
- 参数:p_前缀 (如 p_dept_id)
- 初始化:
- 变量可以不初始化,但建议初始化
- 常量必须在声明时初始化
- 作用域:
- 在DECLARE部分声明的变量只在当前块中有效
- 嵌套块中可以访问外部块变量,除非被同名变量覆盖
- 数据类型:
- 优先使用%TYPE和%ROWTYPE,使代码更灵活
- 避免使用过大的VARCHAR2尺寸,合理估计需求
带参数的游标和固定条件游标:
特性 | 带参数游标 | 固定条件游标 |
灵活性 | 高(可动态改变查询条件) | 低(条件固定) |
代码复用性 | 高(一个游标多处使用) | 低(每个条件需单独游标) |
维护成本 | 低(只需维护一个游标) | 高(需维护多个相似游标) |
内存占用 | 每次只处理一个游标实例 | 可能同时存在多个游标实例 |
弱类型游标变量、强类型游标变量的区别:
特性 | 弱类型游标变量 ( ) | 强类型游标变量 ( ) |
类型约束 | 无,可关联任何查询 | 必须返回指定类型的记录 |
类型安全 | 低(运行时可能出错) | 高(编译时检查) |
灵活性 | 高(可重用不同查询) | 低(只能用于特定结构查询) |
代码可读性 | 较差(不知道返回结构) | 较好(明确知道返回结构) |
典型用途 | 动态SQL、通用数据处理程序 | 明确数据结构的业务逻辑 |
CREATE OR REPLACE PROCEDURE calculate_employee_bonus(p_dept_id IN NUMBER,p_bonus_rate IN NUMBER,p_total_bonus OUT NUMBER
)
IS-- 常量声明c_min_salary CONSTANT NUMBER := 3000;c_max_bonus CONSTANT NUMBER := 10000;-- 变量声明v_dept_name departments.department_name%TYPE;v_eligible_count NUMBER := 0;v_bonus_pool NUMBER := 0;-- 游标声明CURSOR c_eligible_emps ISSELECT employee_id, last_name, salaryFROM employeesWHERE department_id = p_dept_idAND salary > c_min_salary;-- 记录类型TYPE t_bonus_rec IS RECORD (emp_id employees.employee_id%TYPE,emp_name VARCHAR2(100),bonus_amount NUMBER(10,2));r_bonus t_bonus_rec;BEGIN-- 获取部门名称SELECT department_name INTO v_dept_nameFROM departmentsWHERE department_id = p_dept_id;DBMS_OUTPUT.PUT_LINE('Processing department: ' || v_dept_name);-- 处理游标FOR emp_rec IN c_eligible_emps LOOPv_eligible_count := v_eligible_count + 1;-- 计算奖金r_bonus.emp_id := emp_rec.employee_id;r_bonus.emp_name := emp_rec.last_name;r_bonus.bonus_amount := LEAST(emp_rec.salary * p_bonus_rate, c_max_bonus);v_bonus_pool := v_bonus_pool + r_bonus.bonus_amount;DBMS_OUTPUT.PUT_LINE('Employee: ' || r_bonus.emp_name || ', Bonus: ' || r_bonus.bonus_amount);END LOOP;p_total_bonus := v_bonus_pool;DBMS_OUTPUT.PUT_LINE('Total eligible employees: ' || v_eligible_count);DBMS_OUTPUT.PUT_LINE('Total bonus pool: ' || v_bonus_pool);EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('Department not found: ' || p_dept_id);p_total_bonus := 0;WHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);p_total_bonus := -1; -- 错误标识
END calculate_employee_bonus;
- 返回值:必须使用RETURN语句返回一个值
- 参数模式:
- IN(默认):输入参数
- OUT:输出参数
- IN OUT:既可输入又可输出
- 调用方式:可在SQL语句中直接调用
- 命名冲突:避免与内置函数同名
二、Procedure 存储过程
语法
CREATE [OR REPLACE] PROCEDURE 过程名(参数1 [IN|OUT|IN OUT] 数据类型,参数2 [IN|OUT|IN OUT] 数据类型,...
)
IS-- 声明部分(变量、常量、游标等)
BEGIN-- 执行部分(业务逻辑)
EXCEPTION-- 异常处理部分
END 过程名;
- 无返回值:与函数不同,过程不直接返回值
- 参数模式:可以使用OUT参数返回多个值
- 调用方式:必须使用EXECUTE或CALL语句
- 事务控制:过程中可以包含COMMIT/ROLLBACK
CREATE OR REPLACE PROCEDURE update_employee_salary(p_emp_id IN NUMBER,p_increase IN NUMBER,p_new_salary OUT NUMBER,p_status OUT VARCHAR2
)
ISv_current_salary NUMBER;
BEGIN-- 获取当前薪资SELECT salary INTO v_current_salary FROM employees WHERE employee_id = p_emp_id;-- 更新薪资UPDATE employees SET salary = salary + p_increase WHERE employee_id = p_emp_id;-- 设置输出参数p_new_salary := v_current_salary + p_increase;p_status := 'SUCCESS';COMMIT;
EXCEPTIONWHEN NO_DATA_FOUND THENp_status := 'EMPLOYEE_NOT_FOUND';p_new_salary := 0;WHEN OTHERS THENROLLBACK;p_status := 'ERROR: ' || SQLERRM;p_new_salary := 0;
END update_employee_salary;
三、显式游标
1.基本遍历(显式游标)
DECLARECURSOR c_employees ISSELECT employee_id, last_name, salaryFROM employeesWHERE department_id = 10;r_emp c_employees%ROWTYPE; -- 定义记录变量
BEGINOPEN c_employees; -- 打开游标LOOPFETCH c_employees INTO r_emp; -- 提取一行EXIT WHEN c_employees%NOTFOUND; -- 退出条件DBMS_OUTPUT.PUT_LINE('ID: ' || r_emp.employee_id || ', Name: ' || r_emp.last_name || ', Salary: ' || r_emp.salary);END LOOP;CLOSE c_employees; -- 关闭游标
END;
关键特点
特性 | 说明 |
惰性加载 | 只有调用 时才真正从数据库获取数据,节省内存 |
精确控制 | 可手动控制游标的打开( )、获取( )、关闭( )时机 |
可参数化 | 支持传递参数(如 ) |
性能优化 | 比隐式游标(如 )更灵活,适合复杂数据处理逻辑 |
何时选择显式游标?
- 需要复用查询逻辑时
- 需要精细控制数据提取过程时
- 使用
BULK COLLECT
批量操作时 - 查询非常复杂,需明确分离 SQL 和 PL/SQL 逻辑时
-- 隐式游标(自动管理,但灵活性低)
BEGINFOR emp IN (SELECT employee_id, last_name FROM employees WHERE department_id = 10) LOOP-- 直接使用 emp.employee_id, emp.last_nameEND LOOP;
END;
实际应用场景
- 数据迁移
逐行处理源表数据并插入到目标表。 - 报表生成
遍历数据计算统计指标(如部门平均薪资)。 - 数据校验
检查每行数据是否符合业务规则。 - 批量更新
根据游标结果批量修改其他表的数据。
2.简化遍历(FOR循环)
BEGINFOR emp_rec IN c_employees LOOP -- 自动打开/关闭游标DBMS_OUTPUT.PUT_LINE('ID: ' || emp_rec.employee_id || ', Name: ' || emp_rec.last_name);END LOOP;
END;
3.批量处理(BULK COLLECT)
DECLARETYPE t_emp_tab IS TABLE OF c_employees%ROWTYPE;v_emps t_emp_tab;
BEGINOPEN c_employees;FETCH c_employees BULK COLLECT INTO v_emps; -- 批量提取CLOSE c_employees;-- 处理批量数据FOR i IN 1..v_emps.COUNT LOOP-- 业务逻辑...END LOOP;
END;