Oracle PL/SQL BULK COLLECT:FORALL 示例

⚡ 智能摘要

批量收集 Oracle PL/SQL 一次性将多行数据提取到集合中,而 FORALL 则将批量 DML 操作推送回数据库。两者都减少了 SQL 和 PL/SQL 引擎之间的上下文切换,从而提高了性能。

  • 📦 批量收集: 一次性将多行数据提取到集合变量中,取代了缓慢的逐行提取方式。
  • 🔁 人人共享: 通过一次上下文切换,对整个集合执行一次 INSERT、UPDATE 或 DELETE 操作。
  • 📏 限制条款: 限制每次 BULK COLLECT 获取加载的行数,从而保护大型表的会话内存。
  • 📊 批量收集属性: %BULK_ROWCOUNT(n) 属性报告第 n 个 FORALL DML 语句影响了多少行。
  • ⚙️ 需要收集: INTO 子句必须指定集合类型,例如嵌套表或关联数组。
  • 🤖 人工智能协助: GitHub Copilot 等 AI 助手会生成 BULK COLLECT 和 FORALL 代码块,并标记缺少 LIMIT 子句。

Oracle PL/SQL BULK COLLECT 和 FORALL 带 LIMIT 子句概述

什么是 BULK COLLECT?

批量收集减少了上下文切换 SQL 以及 PL/SQL 引擎,并允许 SQL 引擎一次性获取记录。

Oracle PL / SQL 提供批量获取记录的功能,而不是逐条获取。此 BULK COLLECT 可用于 SELECT 语句中,以批量填充记录或获取单个记录。 光标 批量操作。由于 BULK COLLECT 会批量获取记录,因此 INTO 子句必须始终包含集合类型的变量。使用 BULK COLLECT 的主要优势在于,它通过减少数据库和 PL/SQL 引擎之间的交互来提高性能。

语法:

SELECT <column1> BULK COLLECT INTO bulk_variable FROM <table name>;
FETCH <cursor_name> BULK COLLECT INTO <bulk_variable>;

在上述语法中,BULK COLLECT 用于从 SELECT 和 FETCH 语句收集数据。

FORALL 子句

FORALL 语句执行 DML 操作 批量处理数据。它类似于 FOR 循环语句,但不同之处在于,FOR 循环是在记录级别执行操作,而 FORALL 没有 LOOP 的概念。相反,它会一次性处理给定范围内的所有数据。

语法:

FORALL <loop_variable> in <lower range> .. <higher range>

<DML operations>;

在上述语法中,给定的 DML 操作将对介于下限和上限范围之间的所有数据执行。

限制条款

批量收集的概念是将所有数据一次性加载到目标集合变量中,也就是说,所有数据都会一次性填充到集合变量中。但是,当需要加载的记录总数非常大时,不建议这样做,因为 PL/SQL 尝试加载所有数据会消耗更多会话内存。因此,最好始终限制批量收集操作的大小。

通过在 SELECT 语句中引入 ROWNUM 条件可以轻松实现此大小限制,而对于游标来说,这是不可能实现的。

为了克服这个问题, Oracle 提供了 LIMIT 子句,用于定义批量操作中需要包含的记录数。

语法:

FETCH <cursor_name> BULK COLLECT INTO <bulk_variable> LIMIT <size>;

在上面的语法中,游标提取语句使用 BULK COLLECT 语句以及 LIMIT 子句。

BULK COLLECT 属性

与游标属性类似,BULK COLLECT 函数也具有 %BULK_ROWCOUNT(n) 参数,该参数返回 FORALL 语句中第 n 个 DML 语句所影响的行数,即它统计 FORALL 语句中集合变量中每个值所影响的记录数。参数“n”表示需要统计行数的集合中值的序号。

例如1: 在这个例子中,我们将使用 BULK COLLECT 从 emp 表中导出所有员工的姓名,并且我们还将使用 FORALL 将所有员工的工资增加 5000。

下面的屏幕截图显示了 BULK COLLECT 和 FORALL 示例及其输出。 Oracle.

使用 LIMIT 和 FORALL 进行批量收集示例,更新员工薪资 Oracle PL / SQL

DECLARE
CURSOR guru99_det IS SELECT emp_name FROM emp;
TYPE lv_emp_name_tbl IS TABLE OF VARCHAR2(50);
lv_emp_name lv_emp_name_tbl;
BEGIN
OPEN guru99_det;
FETCH guru99_det BULK COLLECT INTO lv_emp_name LIMIT 5000;
FOR c_emp_name IN lv_emp_name.FIRST .. lv_emp_name.LAST
LOOP
Dbms_output.put_line('Employee Fetched:'||c_emp_name);
END LOOP;
FORALL i IN lv_emp_name.FIRST .. lv_emp_name.LAST
UPDATE emp SET salary=salary+5000 WHERE emp_name=lv_emp_name(i);
COMMIT;
Dbms_output.put_line('Salary Updated');
CLOSE guru99_det;
END;
/

输出

Employee Fetched:BBB
Employee Fetched:XXX
Employee Fetched:YYY
Salary Updated

Code 说明:

  • Code 第2行: 声明语句“SELECT emp_name FROM emp”的游标 guru99_det。
  • Code 第3行: 将 lv_emp_name_tbl 声明为 VARCHAR2(50) 表类型。
  • Code 第4行: 将 lv_emp_name 声明为 lv_emp_name_tbl 类型。
  • Code 第6行: 打开游标。
  • Code 第7行: 使用 BULK COLLECT 将游标获取到 lv_emp_name 变量中,LIMIT 大小为 5000。
  • Code 第 8-11 行: 设置 FOR 循环以打印集合 lv_emp_name 中的所有记录。
  • Code 第12行: 使用 FORALL 将所有员工的工资更新 5000。
  • Code 第14行: 承诺 交易.

常见问题

不。BULK COLLECT SELECT 永远不会引发 NO_DATA_FOUND 异常;相反,它会返回一个空集合。在执行任何操作之前,务必使用 .COUNT 方法检查集合。ping否则,您可能会静默处理零行数据。

SAVE EXCEPTIONS 允许 FORALL 在个别行失败时继续运行。失败的行存储在 SQL%BULK_EXCEPTIONS 中。 Oracle 引发 ORA-24381 错误,您可以将其捕获到 例外 处理程序用于检查每个错误。

当循环读取多行数据时,请使用 BULK COLLECT。 光标 FOR 循环每次切换获取一行,因此批量获取加上 FORALL 在处理大型结果集时可以运行得快很多倍。

BULK COLLECT 一次返回多行数据,因此需要一个多行容器。INTO 目标必须是一个 采集 例如嵌套表、VARRAY 或关联数组,而不是单个标量变量。

不。一个 FORALL 语句头只能执行一次 INSERT、UPDATE、DELETE 或 MERGE 操作。每次迭代中,只有其 VALUES 和 WHERE 子句中的值可能会改变。如果需要执行多个语句,请使用单独的 FORALL 语句。

批量处理的速度可以比逐行代码快几倍甚至一百倍以上,因为 BULK COLLECT 和 FORALL 将数千次引擎上下文切换合并为几次,从而大幅降低了大数据量的开销。

是的。 GitHub 副驾驶 从注释中草拟 BULK COLLECT 获取、FORALL DML 循环和 LIMIT 子句,并建议集合类型声明,但您应该自行审查批处理大小和错误处理。

AI 助手会扫描每次只获取或更改一行数据的循环,并建议使用 BULK COLLECT、LIMIT 和 FORALL 重写这些循环。这种机器学习审查可以在生产环境部署之前发现缺失的 LIMIT 限制和性能瓶颈。

总结一下这篇文章: