PL/SQL是Oracle数据库提供的过程化编程语言,它在标准SQL的基础上增加了变量、流程控制、异常处理等编程特性,能够让开发者编写复杂的数据库逻辑,直接在数据库服务器端执行,减少应用层和数据库层的交互开销。

PL/SQL基础语法结构
一个完整的PL/SQL程序块包含声明部分、执行部分和异常处理部分,其中执行部分是必须的,其他两部分可选。基本结构如下:
-- 声明部分,可选,用于定义变量、常量、游标等
DECLARE
v_emp_name VARCHAR2(50); -- 声明员工姓名变量
v_emp_salary NUMBER(10,2); -- 声明员工薪资变量
BEGIN
-- 执行部分,必选,编写核心逻辑
SELECT ename, sal INTO v_emp_name, v_emp_salary FROM emp WHERE empno = 7369;
DBMS_OUTPUT.PUT_LINE('员工姓名:' || v_emp_name || ',薪资:' || v_emp_salary);
EXCEPTION
-- 异常处理部分,可选,处理运行时错误
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到对应员工信息');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生未知错误:' || SQLERRM);
END;
/
需要注意,在Oracle客户端执行PL/SQL块时,末尾需要加/来标识块结束,同时要先执行SET SERVEROUTPUT ON命令才能看到DBMS_OUTPUT.PUT_LINE的输出内容。
变量与数据类型
PL/SQL支持多种数据类型,除了Oracle SQL的标准数据类型外,还有专属的类型,常见分类如下:
- 标量类型:VARCHAR2、NUMBER、DATE、BOOLEAN等,用于存储单个值
- 复合类型:RECORD(记录类型,类似结构体)、TABLE(表类型,类似数组)等,用于存储多个值
- 引用类型:REF CURSOR(游标类型),用于动态关联查询结果
- LOB类型:CLOB、BLOB等,用于存储大对象数据
变量声明的语法为变量名 数据类型 [:= 初始值],如果声明时未赋值,变量默认值为NULL。例如:
DECLARE
-- 声明常量,必须初始化且不可修改
c_tax_rate CONSTANT NUMBER(3,2) := 0.08;
-- 声明变量并初始化
v_total_salary NUMBER(10,2) := 0;
-- 声明记录类型变量
TYPE emp_record_type IS RECORD(
emp_name VARCHAR2(50),
emp_dept VARCHAR2(30),
emp_sal NUMBER(10,2)
);
v_emp_rec emp_record_type;
BEGIN
NULL; -- 占位语句,避免块为空报错
END;
/
流程控制语句
PL/SQL提供了条件判断和循环两种流程控制结构,满足不同的逻辑执行需求。
条件判断
常用的条件判断语句有IF-THEN-ELSE和CASE两种:
DECLARE
v_score NUMBER := 85;
v_grade VARCHAR2(10);
BEGIN
-- IF条件判断
IF v_score >= 90 THEN
v_grade := '优秀';
ELSIF v_score >= 80 THEN
v_grade := '良好';
ELSIF v_score >= 60 THEN
v_grade := '及格';
ELSE
v_grade := '不及格';
END IF;
DBMS_OUTPUT.PUT_LINE('成绩等级:' || v_grade);
-- CASE条件判断
CASE
WHEN v_score >= 90 THEN v_grade := 'A';
WHEN v_score >= 80 THEN v_grade := 'B';
WHEN v_score >= 60 THEN v_grade := 'C';
ELSE v_grade := 'D';
END CASE;
DBMS_OUTPUT.PUT_LINE('成绩评级:' || v_grade);
END;
/
循环结构
PL/SQL支持三种循环:LOOP循环、WHILE循环、FOR循环,可根据场景选择使用:
DECLARE
v_counter NUMBER := 1;
BEGIN
-- LOOP循环,需要手动退出
LOOP
DBMS_OUTPUT.PUT_LINE('LOOP循环计数:' || v_counter);
v_counter := v_counter + 1;
EXIT WHEN v_counter > 3; -- 退出条件
END LOOP;
-- WHILE循环,先判断条件再执行
v_counter := 1;
WHILE v_counter <= 3 LOOP
DBMS_OUTPUT.PUT_LINE('WHILE循环计数:' || v_counter);
v_counter := v_counter + 1;
END LOOP;
-- FOR循环,自动维护计数器
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE('FOR循环计数:' || i);
END LOOP;
END;
/
游标的使用
游标用于处理SELECT语句返回的多行结果集,分为显式游标和隐式游标两种。隐式游标是Oracle自动创建的,用于处理单条SQL语句;显式游标需要手动声明、打开、获取数据和关闭。
显式游标的基本使用步骤:
DECLARE
-- 声明游标,关联查询语句
CURSOR emp_cur IS SELECT empno, ename, sal FROM emp WHERE deptno = 10;
v_empno NUMBER;
v_ename VARCHAR2(50);
v_sal NUMBER(10,2);
BEGIN
-- 打开游标
OPEN emp_cur;
LOOP
-- 获取游标数据,每次取一行
FETCH emp_cur INTO v_empno, v_ename, v_sal;
-- 退出条件:没有更多数据
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('员工号:' || v_empno || ',姓名:' || v_ename || ',薪资:' || v_sal);
END LOOP;
-- 关闭游标
CLOSE emp_cur;
END;
/
游标属性%NOTFOUND用于判断最近一次FETCH是否获取到数据,没有获取到则返回TRUE,%FOUND则相反,%ROWCOUNT返回当前已经获取的行数,%ISOPEN判断游标是否处于打开状态。
存储过程与函数
存储过程和函数是存储在数据库中的PL/SQL程序块,可以被其他程序重复调用,区别在于函数有返回值,存储过程没有返回值。
存储过程创建与调用
创建存储过程使用CREATE OR REPLACE PROCEDURE语句,示例:
-- 创建存储过程,根据部门号查询员工数量
CREATE OR REPLACE PROCEDURE get_dept_emp_count(
p_deptno IN NUMBER, -- 输入参数,部门号
p_emp_count OUT NUMBER -- 输出参数,员工数量
) AS
BEGIN
SELECT COUNT(*) INTO p_emp_count FROM emp WHERE deptno = p_deptno;
EXCEPTION
WHEN OTHERS THEN
p_emp_count := -1; -- 错误时返回-1
END get_dept_emp_count;
/
调用存储过程的方式:
DECLARE
v_count NUMBER;
BEGIN
-- 调用存储过程,传入参数并获取输出值
get_dept_emp_count(10, v_count);
DBMS_OUTPUT.PUT_LINE('10号部门员工数量:' || v_count);
END;
/
函数创建与调用
创建函数使用CREATE OR REPLACE FUNCTION语句,必须指定返回值类型:
-- 创建函数,计算两个数的和
CREATE OR REPLACE FUNCTION add_two_num(
p_num1 IN NUMBER,
p_num2 IN NUMBER
) RETURN NUMBER AS
v_result NUMBER;
BEGIN
v_result := p_num1 + p_num2;
RETURN v_result;
END add_two_num;
/
函数可以在SQL语句或者PL/SQL块中调用:
-- 在PL/SQL块中调用
DECLARE
v_sum NUMBER;
BEGIN
v_sum := add_two_num(10, 20);
DBMS_OUTPUT.PUT_LINE('两数之和:' || v_sum);
END;
/
-- 在SQL语句中调用
SELECT add_two_num(15, 25) AS sum_result FROM dual;
触发器
触发器是当特定事件发生时自动执行的PL/SQL程序块,常见触发事件有INSERT、UPDATE、DELETE操作,分为行级触发器和语句级触发器。
创建触发器的示例,实现插入员工数据时自动填充入职时间:
-- 创建行级触发器,插入emp表数据时自动设置hiredate为当前时间
CREATE OR REPLACE TRIGGER trg_emp_insert
BEFORE INSERT ON emp
FOR EACH ROW -- 行级触发器,每插入一行执行一次
BEGIN
-- :NEW表示新插入的行记录
:NEW.hiredate := SYSDATE;
END trg_emp_insert;
/
测试触发器效果:
-- 插入数据时不指定hiredate INSERT INTO emp(empno, ename, deptno) VALUES(9999, '测试员工', 10); -- 查询插入的数据,hiredate会自动填充为当前时间 SELECT empno, ename, hiredate FROM emp WHERE empno = 9999;
异常处理
PL/SQL的异常处理分为预定义异常和自定义异常,预定义异常是Oracle已经定义好的常见错误,比如NO_DATA_FOUND(未找到数据)、TOO_MANY_ROWS(返回多行数据)等。
自定义异常的使用方式:
DECLARE
-- 自定义异常
e_salary_too_low EXCEPTION;
v_salary NUMBER := 500;
-- 将异常编号关联到自定义异常
PRAGMA EXCEPTION_INIT(e_salary_too_low, -20001);
BEGIN
IF v_salary < 2000 THEN
-- 抛出异常
RAISE e_salary_too_low;
END IF;
EXCEPTION
WHEN e_salary_too_low THEN
DBMS_OUTPUT.PUT_LINE('薪资过低,不符合要求');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('其他错误:' || SQLERRM);
END;
/
也可以使用RAISE_APPLICATION_ERROR直接抛出带自定义错误信息的异常:
BEGIN
RAISE_APPLICATION_ERROR(-20002, '自定义错误信息:操作不合法');
END;
/