知识库✳

教程与手册

全部教程
  1. 01Oracle 函数手册
  2. 02性能优化手册
  3. 03Linux Shell
  4. 04Oracle 学习与演练
  5. 05网络基础学习
  6. 06数据治理与管理
返回个人网站
甲Oracle 学习教程从 SQL 到 PL/SQL 与运维
首页课程目录搜索演练场
知识库

已完成 0 / 22 章

模块一 · 入门与准备

  • 01Oracle 数据库体系结构
  • 02环境安装与连接方式
  • 03SQL*Plus 与常用工具

模块二 · SQL 查询基础

  • 04SELECT 查询基础
  • 05过滤与排序
  • 06单行函数
  • 07多表连接
  • 08分组聚合与 HAVING
  • 09子查询

模块三 · 数据与对象

  • 10DML 与事务控制
  • 11表与数据类型
  • 12约束
  • 13索引与执行计划
  • 14视图、序列与同义词

模块四 · PL/SQL 编程

  • 15PL/SQL 块与变量
  • 16流程控制与游标
  • 17存储过程与函数
  • 18触发器
  • 19异常处理

模块五 · 运维与进阶

  • 20用户与权限管理
  • 21表空间与存储管理
  • 22备份与恢复
❖

Oracle 学习教程

返回个人网站知识库

示例代码基于 Oracle AI Database 26ai / 23ai,查询示例使用官方 HR 示例方案。

以 Next.js 16 · Tailwind CSS v4 · HeroUI v3 装帧

16
  1. 首页
  2. 课程目录
  3. 流程控制与游标

第 16 章◆进阶◆建议 55 分钟◆模块四 · PL/SQL 编程

流程控制与游标

条件判断、循环,以及处理多行结果集的游标——PL/SQL 处理批量数据的核心。

学完这一章,你应该能

  • 熟练使用 IF/CASE 与三种循环
  • 会用游标 FOR 循环遍历结果集
  • 理解隐式游标属性 SQL%ROWCOUNT
  • 知道游标 FOR UPDATE 的用途

16.1条件与循环

  • FOR i IN REVERSE 3 .. 1 LOOP 可以倒序
  • CONTINUE 跳过本次循环,EXIT 跳出循环
  • 避免 GOTO,它会让逻辑难以追踪
判断与三种循环
DECLARE
  v_sal   NUMBER := 12000;
  v_grade VARCHAR2(10);
  v_i     NUMBER := 1;
BEGIN
  -- IF / ELSIF
  IF v_sal < 5000 THEN
    v_grade := '低';
  ELSIF v_sal < 15000 THEN
    v_grade := '中';
  ELSE
    v_grade := '高';
  END IF;

  -- CASE 表达式
  v_grade := CASE
               WHEN v_sal < 5000  THEN '低'
               WHEN v_sal < 15000 THEN '中'
               ELSE '高'
             END;

  -- 1) 基础 LOOP
  LOOP
    EXIT WHEN v_i > 3;
    v_i := v_i + 1;
  END LOOP;

  -- 2) WHILE
  v_i := 1;
  WHILE v_i <= 3 LOOP
    v_i := v_i + 1;
  END LOOP;

  -- 3) FOR(推荐:自动控制上下界)
  FOR i IN 1 .. 3 LOOP
    DBMS_OUTPUT.PUT_LINE('第 ' || i || ' 次');
  END LOOP;

  DBMS_OUTPUT.PUT_LINE('等级: ' || v_grade);
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

16.2隐式游标属性

  • SQL%FOUND / SQL%NOTFOUND / SQL%ROWCOUNT / SQL%ISOPEN 是隐式游标属性
  • 它们反映最近一条 DML 或 SELECT INTO 的结果
  • SQL%ROWCOUNT 在 SELECT INTO 成功时为 1
用 SQL%ROWCOUNT 判断影响行数
BEGIN
  UPDATE employees SET salary = salary * 1.05 WHERE department_id = 60;

  IF SQL%ROWCOUNT = 0 THEN
    DBMS_OUTPUT.PUT_LINE('没有匹配的行,未做任何修改');
    ROLLBACK;
  ELSE
    DBMS_OUTPUT.PUT_LINE('更新了 ' || SQL%ROWCOUNT || ' 行');
    ROLLBACK;
    -- 演示用,不留痕
  END IF;
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

16.3显式游标与游标 FOR 循环

显式游标的完整四步(教学用)
DECLARE
  CURSOR c_emp IS
    SELECT employee_id, last_name, salary
    FROM   employees
    WHERE  department_id = 60
    ORDER  BY salary DESC;

  v_id   employees.employee_id%TYPE;
  v_name employees.last_name%TYPE;
  v_sal  employees.salary%TYPE;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp INTO v_id, v_name, v_sal;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_name || ' ' || v_sal);
  END LOOP;
  CLOSE c_emp;
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

游标 FOR 循环(推荐写法)
BEGIN
  FOR r IN (SELECT employee_id, last_name, salary
            FROM   employees
            WHERE  department_id = 60
            ORDER  BY salary DESC) LOOP
    DBMS_OUTPUT.PUT_LINE(r.employee_id || ' ' || r.last_name || ' ' || r.salary);
  END LOOP;
END;
/

游标 FOR 循环自动 OPEN、FETCH、CLOSE,还自动声明记录变量 r,出错也会自动关闭游标。

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

16.4参数化游标与 FOR UPDATE

带参数的游标 + 锁定行
DECLARE
  CURSOR c_dept_sal (p_dept NUMBER, p_min NUMBER) IS
    SELECT employee_id, salary
    FROM   employees
    WHERE  department_id = p_dept
      AND  salary >= p_min
    FOR    UPDATE OF salary;
    -- 锁定这些行,防止并发修改
BEGIN
  FOR r IN c_dept_sal(60, 5000) LOOP
    DBMS_OUTPUT.PUT_LINE(r.employee_id || ' -> ' || r.salary);
  END LOOP;
  ROLLBACK;
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

提示 游标 FOR UPDATE 会持有行锁直到事务结束。批量处理时要注意锁的持有时间,长事务容易造成阻塞。

练习

3 题 · 先自己写,再展开对照

参考答案

游标 FOR 循环自动完成 OPEN、逐行 FETCH、结束判定和 CLOSE,
还自动声明记录变量,代码更短、更不容易出错。

手写四步时最容易犯的错是:提前 RETURN 或抛异常导致 CLOSE 没执行,
游标一直开着(虽然会在会话结束或超出 OPEN_CURSORS 时报错,但很难排查)。

游标 FOR 循环即使在循环体内报错,也会自动关闭游标。

自评要点

  • 自动 open/fetch/close 与自动声明记录变量
  • 指出手写时容易漏 close

参考答案

SQL%ROWCOUNT 是 0,不会抛 NO_DATA_FOUND。

NO_DATA_FOUND 只针对 SELECT INTO 没查到数据(以及引用集合中不存在的元素)。
DML 影响 0 行是正常情况,不是异常——所以判断有没有改到东西
要靠 SQL%ROWCOUNT,而不是靠异常处理。

这是个很常见的认知混淆点。

自评要点

  • SQL%ROWCOUNT = 0
  • NO_DATA_FOUND 只与 SELECT INTO 有关

参考答案

用途:在「先读出来、判断后再改」的业务逻辑里锁定这些行,
防止其他会话在你判断和修改之间把数据改掉(丢失更新)。

代价:它持有行级锁直到事务结束(COMMIT / ROLLBACK)。
如果这批处理耗时长,就会长时间阻塞其他会话。

所以批量作业里要么缩短事务,要么用 FOR UPDATE NOWAIT /
SKIP LOCKED(拿不到锁就跳过)来避免互相等待。

自评要点

  • 防丢失更新
  • 持锁到事务结束,可能阻塞他人
  • 知道 NOWAIT / SKIP LOCKED
← 上一章

PL/SQL 块与变量

下一章 →

存储过程与函数