PL/SQL 块结构与流程控制
文章目录
- PL/SQL 块结构与流程控制
-
- 前言
- 一、块结构:DECLARE / BEGIN / EXCEPTION / END
- 二、第一个块与 serveroutput 开关
- 三、IF / ELSIF / ELSE
- 四、CASE 的两种形态
- 五、三种循环
- 六、嵌套块、变量作用域与遮蔽
- 七、标签
- 八、SQL 与 PL/SQL 的交互:SELECT INTO
- 写在最后
前言
从这篇起进入 PL/SQL。它是 Oracle 的过程化扩展:SQL 能查能改,但做不了条件分支、循环、异常捕获,PL/SQL 把这些补齐了,还让 SQL 和过程代码无缝混写。一切 PL/SQL 代码的骨架是"块",先把块结构吃透,后面的存储过程、函数、包、触发器全是块的不同穿法。
一、块结构:DECLARE / BEGIN / EXCEPTION / END
一个完整的 PL/SQL 块分三段:
DECLARE
— 声明段:变量、常量、游标(可选)
v_ename VARCHAR2(20);
BEGIN
— 执行段:SQL 与过程语句(必需)
SELECT ename INTO v_ename FROM emp WHERE empno = 7369;
dbms_output.put_line('姓名:' || v_ename);
EXCEPTION
— 异常段:出错处理(可选)
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('没找到这个员工');
END;
/
只有 BEGIN…END 是必需的,声明段以 DECLARE 开头,异常段以 EXCEPTION 开头。声明变量时可以顺手初始化:
DECLARE
v_count NUMBER := 0;
c_rate CONSTANT NUMBER := 0.1; — 常量,初始化后不能再赋值
BEGIN
NULL; — 什么都不做,占位语句
END;
/
赋值符号是 := 不是 =,= 在 PL/SQL 里只做比较,这是从其他语言转过来最容易顺手写错的地方。
二、第一个块与 serveroutput 开关
SQL*Plus 里跑第一个块:
SET SERVEROUTPUT ON SIZE UNLIMITED
BEGIN
dbms_output.put_line('hello plsql');
END;
/
dbms_output.put_line 把内容写进缓冲区,SQLPlus 默认不回显,必须先 SET SERVEROUTPUT ON。块执行成功却什么都看不到,基本都是忘了这个开关。SQL Developer 的脚本输出窗口、其他工具的 DBMS Output 面板,等价于这个开关。块末尾的 / 是 SQLPlus 的执行符,用工具的运行按钮时不需要。
三、IF / ELSIF / ELSE
DECLARE
v_sal emp.sal%TYPE;
BEGIN
SELECT sal INTO v_sal FROM emp WHERE empno = 7788;
IF v_sal >= 3000 THEN
dbms_output.put_line('高薪');
ELSIF v_sal >= 1500 THEN
dbms_output.put_line('中等');
ELSE
dbms_output.put_line('待涨薪');
END IF;
END;
/
三个易错点:关键字是 ELSIF,不是 ELSEIF 也不是 ELSE IF;条件表达式后面不加分号,语句后面才加分号;END IF 是两个单词,中间有空格,结尾有分号。
四、CASE 的两种形态
简单 CASE 按值匹配,搜索 CASE 按条件匹配,都是表达式,可以直接参与赋值:
DECLARE
v_deptno NUMBER := 20;
v_name VARCHAR2(20);
BEGIN
— 简单 CASE:值匹配
v_name := CASE v_deptno
WHEN 10 THEN 'ACCOUNTING'
WHEN 20 THEN 'RESEARCH'
WHEN 30 THEN 'SALES'
ELSE 'UNKNOWN'
END;
dbms_output.put_line(v_name);
— 搜索 CASE:条件匹配
v_name := CASE WHEN v_deptno IN (10, 30) THEN '业务部门'
WHEN v_deptno = 20 THEN '研发部门'
ELSE '其他'
END;
dbms_output.put_line(v_name);
END;
/
取值映射用 CASE,复杂流程分支用 IF,CASE 没有落空分支时会报 ORA-06592(CASE_NOT_FOUND),值匹配场景别漏 ELSE。
五、三种循环
— 1. 基本循环:先执行后判断,EXIT 防死循环
DECLARE
i NUMBER := 1;
BEGIN
LOOP
dbms_output.put_line('i = ' || i);
EXIT WHEN i > 5;
i := i + 1;
END LOOP;
END;
/
— 2. WHILE 循环:先判断后执行
DECLARE
i NUMBER := 1;
BEGIN
WHILE i <= 5 LOOP
dbms_output.put_line('i = ' || i);
i := i + 1;
END LOOP;
END;
/
— 3. FOR 循环:自带计数器,不用声明
BEGIN
FOR i IN 1 .. 5 LOOP
dbms_output.put_line('i = ' || i);
END LOOP;
END;
/
FOR 支持 REVERSE 倒序,边界依然写"小 … 大",别顺手写成 5 .. 1:
BEGIN
FOR i IN REVERSE 1 .. 5 LOOP
dbms_output.put_line('i = ' || i); — 输出 5 4 3 2 1
END LOOP;
END;
/
循环体内可用 EXIT、EXIT WHEN 退出,CONTINUE 跳过本轮(11g 引入)。选型思路:次数确定用 FOR,条件先行用 WHILE,至少执行一次用 LOOP。FOR 的计数器是隐式声明的,不要在 DECLARE 里再声明同名变量。
六、嵌套块、变量作用域与遮蔽
块可以嵌套,内层能看见外层的变量,反过来不行:
DECLARE
v_outer VARCHAR2(20) := '外部';
BEGIN
dbms_output.put_line(v_outer); — 外部
DECLARE
v_inner VARCHAR2(20) := '内部';
BEGIN
dbms_output.put_line(v_outer); — 外部,内层可见外层
dbms_output.put_line(v_inner); — 内部
END;
dbms_output.put_line(v_inner); — 编译报错:v_inner 未声明
END;
/
内层声明同名变量会遮蔽外层变量:
DECLARE
v_x NUMBER := 1;
BEGIN
DECLARE
v_x NUMBER := 2;
BEGIN
dbms_output.put_line(v_x); — 2,内层遮蔽外层
END;
dbms_output.put_line(v_x); — 1,外层不受影响
END;
/
同名遮蔽读起来费劲,实践中更直接的规避办法就是别同名。确实需要内外层都访问时,用下一节的标签。
七、标签
给块起名字,配合限定名访问被遮蔽的外层变量;循环加标签后,EXIT 可以指定跳出哪一层:
<<outer>>
DECLARE
v_x NUMBER := 1;
BEGIN
DECLARE
v_x NUMBER := 2;
BEGIN
dbms_output.put_line(v_x); — 2
dbms_output.put_line(outer.v_x); — 1,标签限定访问外层
END;
END outer;
/
<<outer_loop>>
FOR i IN 1 .. 3 LOOP
FOR j IN 1 .. 3 LOOP
EXIT outer_loop WHEN i = 2 AND j = 2; — 直接跳出外层循环
dbms_output.put_line(i || '-' || j);
END LOOP;
END LOOP outer_loop;
多层循环想从最里层一口气跳出时,标签是唯一干净的写法,否则只能靠标志变量一层层传递。
八、SQL 与 PL/SQL 的交互:SELECT INTO
PL/SQL 里查单行数据用 SELECT INTO:
DECLARE
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
BEGIN
SELECT ename, sal
INTO v_ename, v_sal
FROM emp
WHERE empno = 7788;
dbms_output.put_line(v_ename || ' 的薪水是 ' || v_sal);
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('查无此人');
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('不止一行,WHERE 条件不够严');
END;
/
约定:SELECT INTO 只适用于"最多一行"的查询。0 行抛 NO_DATA_FOUND,多于一行抛 TOO_MANY_ROWS,两种情况都得有心理准备。多行查询交给下一篇的游标。DML 语句则可以直接写在块里:
BEGIN
UPDATE emp SET sal = sal * 1.1 WHERE deptno = 10;
dbms_output.put_line('更新了 ' || SQL%ROWCOUNT || ' 行');
END;
/
变量声明还有两种锚定写法:v_ename emp.ename%TYPE 跟随列类型,v_emp emp%ROWTYPE 一次拿整行记录。表结构变了代码不用跟着改,比写死 VARCHAR2(20) 稳得多。
写在最后
块 = DECLARE(可选)+ BEGIN + EXCEPTION(可选)+ END,只有 BEGIN…END 必需;SQL*Plus 里先 SET SERVEROUTPUT ON 才能看到输出;分支 IF/ELSIF/ELSE 与 CASE 表达式,循环 LOOP/WHILE/FOR 三选一,次数确定优先 FOR;内层块可见外层变量,同名会遮蔽,标签加限定名可破;SELECT INTO 只管一行,0 行与多行分别抛 NO_DATA_FOUND 与 TOO_MANY_ROWS。这两个异常正是下一篇的主角:游标与异常处理。
网硕互联帮助中心





评论前必须登录!
注册