`

第五章 PL/SQL集合与记录(3)

阅读更多

十一、避免集合异常

大多情况下,如果我们引用了一个集合中不存在的元素,PL/SQL就会抛出一个预定义异常。例如:

DECLARE
  TYPE numlist IS TABLE OF NUMBER;

  nums   numlist;   -- atomically null
BEGIN
  /* Assume execution continues despite the raised exceptions. */
  nums(1)       := 1;   -- raises COLLECTION_IS_NULL (1)
  nums          := numlist(1, 2);   -- initialize table
  nums(NULL)    := 3;   -- raises VALUE_ERROR (2)
  nums(0)       := 3;   -- raises SUBSCRIPT_OUTSIDE_LIMIT (3)
  nums(3)       := 3;   -- raises SUBSCRIPT_BEYOND_COUNT (4)
  nums.DELETE(1);   -- delete element 1
  IF nums(1) = 1 THEN 
    ... -- raises NO_DATA_FOUND (5)
END;

第一句,嵌套表是空的;第二句,下标为空;三四句,下标超出合法范围之外;第五句,下标指向了一个被删除的元素。下表是一些异常情况的说明:

集合异常发生时机
COLLECTION_IS_NULL 调用一个空集合的方法
NO_DATA_FOUND 下标索引指向一个被删除的元素,或是关联数组中不存在的元素
SUBSCRIPT_BEYOND_COUNT 下标索引值超过集合中的元素个数
SUBSCRIPT_OUTSIDE_LIMIT 下标索引超过允许范围之外
VALUE_ERROR 下标索引值为空,或是不能转换成正确的键类型。当键被定义在
PLS_INTEGER的范围内,而下标索引值超过这个范围就可能抛
出这个异常

在某些情况下,如果我们为一个方法传递了一个无效的下标,并不会抛出异常。例如在使用DELETE方法的时候,我们向它传递NULL,它只是什么都没做而已。同样,用新值替换被删除的元素也不会引起NO_DATA_FOUND异常,如下例:

DECLARE
  TYPE numlist IS TABLE OF NUMBER;

  nums   numlist := numlist(10, 20, 30);   -- initialize table
BEGIN
  nums.DELETE(-1);   -- does not raise SUBSCRIPT_OUTSIDE_LIMIT
  nums.DELETE(3);   -- delete 3rd element
  DBMS_OUTPUT.put_line(nums.COUNT);   -- prints 2
  nums(3)    := 30;   -- allowed; does not raise NO_DATA_FOUND
  DBMS_OUTPUT.put_line(nums.COUNT);   -- prints 3
END;

打包集合类型和本地集合类型总是不兼容的。假设我们想调用下面的打包过程:

CREATE PACKAGE pkg1 AS
  TYPE NumList IS VARRAY(25) OF NUMBER(4);
  
  PROCEDURE delete_emps (emp_list NumList);
END pkg1;

CREATE PACKAGE BODY pkg1 AS
  PROCEDURE delete_emps (emp_list NumList) IS ...
    ...
END pkg1;

在运行下面PL/SQL块时,第二个过程调用会因参数的数量或类型错误(wrong number or types of arguments error)而执行失败。这是因为打包VARRAY和本地VARRAY类型不兼容,虽然它们的定义形式都是一样的:

DECLARE
  TYPE numlist IS VARRAY(25) OF NUMBER(4);

  emps    pkg1.numlist := pkg1.numlist(7369, 7499);
  emps2   numlist      := numlist(7521, 7566);
BEGIN
  pkg1.delete_emps(emps);
  pkg1.delete_emps(emps2);   -- causes a compilation error
END;

十二、使用集合批量绑定减少循环开销

如下图所示,PL/SQL引擎会执行过程化语句,但它把SQL语句发送给SQL引擎处理,然后SQL引擎把处理的结果返回给PL/SQL引擎。

PL/SQL和SQL引擎间的频繁切换会大大降低效率。典型的情况就是在一个循环中反复执行SQL语句。例如,下面的DELETE语句就会在FOR循环中被多次发送到SQL引擎中去:

DECLARE
  TYPE numlist IS VARRAY(20) OF NUMBER;

  depts   numlist := numlist(10, 30, 70);   -- department numbers
BEGIN
  ...
  FOR i IN depts.FIRST .. depts.LAST LOOP
    DELETE FROM emp
          WHERE deptno = depts(i);
  END LOOP;
END;

 

这种情况下,如果SQL语句影响了四行或更多行时,使用批量绑定就会显著地提高性能。

 

1、批量绑定如何提高性能

用SQL语句中为PL/SQL变量赋值称为绑定,PL/SQL绑定操作可以分为三种:

  1. 内绑定(in-bind):用INSERT或UPDATE语句将PL/SQL变量或主变量保存到数据库。
  2. 外绑定(out-bind):通过INSERT、UPDATE或DELETE语句的RETURNING子句的返回值为PL/SQL变量或主变量赋值。
  3. 定义(define):使用SELECT或FETCH语句为PL/SQL变量或主变量赋值。

DML语句可以一次性传递集合中所有的元素,这个过程就是批量绑定。如果集合有20个元素,批量绑定的一次操作就相当于执行20次SELECT、 INSERT、UPDATE或DELETE语句。这项技术是靠减少PL/SQL和SQL引擎间的切换次数来提高性能的。要对INSERT、UPDATE和 DELETE语句使用批量绑定,就要用PL/SQL的FORALL语句。

如果要在SELECT语句中使用批量绑定,我们就要在SELECT语句后面加上一个BULK COLLECT子句来代替INTO子句。

  • 例一:对DELETE语句应用批量绑定

下面的DELETE语句只往SQL引擎中发送一次,即使是执行了三次DELETE操作:

DECLARE
  TYPE numlist IS VARRAY(20) OF NUMBER;

  depts   numlist := numlist(10, 30, 70);   -- department numbers
BEGIN
  FORALL i IN depts.FIRST .. depts.LAST
    DELETE FROM emp
          WHERE deptno = depts(i);
END;
  • 例二:对INSERT语句应用批量绑定

下例中,我们把5000个零件编号和名称放到索引表中。所有的表元素都向数据库插入两次:第一次使用FOR循环,然后使用FORALL语句。实际上,FORALL版本的代码执行速度要比FOR语句版本的快得多。

SQLSET SERVEROUTPUT ON
SQLCREATE TABLE parts (pnum NUMBER(4), pname CHAR(15));
Table created.
SQL> GET test.sql
DECLARE
TYPE NumTab IS TABLE OF NUMBER(4) INDEX BY BINARY_INTEGER;
TYPE NameTab IS TABLE OF CHAR(15) INDEX BY BINARY_INTEGER;
4 pnums NumTab;
5 pnames NameTab;
6 t1 NUMBER(5);
7 t2 NUMBER(5);
8 t3 NUMBER(5);
9
10
11 BEGIN
12 FOR j IN 1..5000 LOOP   -- load index-by tables
13 pnums(j) := j;
14 pnames(j) := 'Part No. ' || TO_CHAR(j);
15 END LOOP;
16 t1 := dbms_utility.get_time;
17 FOR i IN 1..5000 LOOP   -- use FOR loop
18 INSERT INTO parts VALUES (pnums(i), pnames(i));
19 END LOOP;
20 t2 := dbms_utility.get_time;
21 FORALL i IN 1..5000   -- use FORALL statement
22 INSERT INTO parts VALUES (pnums(i), pnames(i));
23 get_time(t3);
24 dbms_output.put_line('Execution Time (secs)');
25 dbms_output.put_line('---------------------');
26 dbms_output.put_line('FOR loop: ' || TO_CHAR(t2 - t1));
27 dbms_output.put_line('FORALL: ' || TO_CHAR(t3 - t2));
28* END;
SQL> /
Execution Time (secs)
---------------------
FOR loop: 32
FORALL: 3

十三、使用FORALL语句

关键字FORALL能让PL/SQL引擎在将集合发送到SQL引擎之前,批量导入集合元素。虽然FORALL也包含了迭代的模式,但它并不是简单的FOR循环。它的使用语法如下:

FORALL index IN lower_bound..upper_bound
  sql_statement;

index只能在FORALL语句块内作为集合下标使用。SQL语句必须是引用了集合元素的INSERT、UPDATE或DELETE语句。bound的有效范围是连续的索引号。在这个范围内,SQL引擎为每个索引号执行一次SQL语句。

  • 例一:使用FORALL操作集合的部分内容

如下例所示,FORALL循环的边界值可作用于集合的部分内容,不必是全部的元素:

DECLARE
  TYPE numlist IS VARRAY(10) OF NUMBER;

  depts   numlist := numlist(20, 30, 50, 55, 57, 60, 70, 75, 90, 92);
BEGIN
  FORALL j IN 4 .. 7   -- bulk-bind only part of varray
    UPDATE emp
       SET sal = sal * 1.10
     WHERE deptno = depts(j);
END;
  • 例二:使用集合下标索引的批量绑定

SQL语句能引用一个以上的集合。但是PL/SQL引擎的批量绑定只能为一个集合添加下标索引。所以,在下面的例子中,对于传递给函数median的集合sals,并没有使用到批量绑定。

FORALL i IN 1..20
  INSERT INTO emp2 VALUES (enums(i), names(i), median(sals), ...);
  • 例三:使用FORALL为对象表添加数据

除了关系表之外,FORALL语句还可以操作对象表,如下例所示:

CREATE TYPE pnum AS OBJECT(
  n   NUMBER
);
/

CREATE TABLE partno OF pnum;
DECLARE
  TYPE numtab IS TABLE OF NUMBER;

  nums    numtab  := numtab(1, 2, 3, 4);

  TYPE pnumtab IS TABLE OF pnum;

  pnums   pnumtab := pnumtab(pnum(1), pnum(2), pnum(3), pnum(4));
BEGIN
  FORALL i IN pnums.FIRST .. pnums.LAST
    INSERT INTO partno
         VALUES (pnums(i));
  FORALL i IN nums.FIRST .. nums.LAST
    DELETE FROM partno
          WHERE n = 2 * nums(i);
  FORALL i IN nums.FIRST .. nums.LAST
    INSERT INTO partno
         VALUES (100 + nums(i));
END;

1、FORALL语句对回滚的影响

在FORALL语句中,如果SQL语句引起了一个未捕获异常,以前对数据库的所有操作都会被回滚。但是,如果我们捕获到被抛出的异常并加以处理,此次之前的操作就不会被回滚。举一个例子,假设我们创建了数据表用来存储部门编号和职别:

CREATE TABLE emp2 (deptno NUMBER(2), job VARCHAR2(15));

下一步,为刚才建立的数据表添加一些记录:

INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(20, 'Bookkeeper');   -- 10-char job title
INSERT INTO emp2 VALUES(30, 'Analyst');
INSERT INTO emp2 VALUES(30, 'Analyst');

然后,我们用下面的UPDATE语句为特定的职称加上七位字符串' (temp)':

DECLARE
  TYPE numlist IS TABLE OF NUMBER;

  depts   numlist := numlist(10, 20, 30);
BEGIN
  FORALL j IN depts.FIRST .. depts.LAST
    UPDATE emp2
       SET job = job || ' (temp)'
     WHERE deptno = depts(j);
  -- raises a "value too large" exception
EXCEPTION
  WHEN OTHERS THEN
    COMMIT;
END;

SQL引擎会执行UPDATE语句三次,第一次成功,但在第二次会因字符串值'Bookkeeper (temp)'太长而无法赋给job字段,所以就会执行失败。这种情况下,只有第二条语句回滚。

只要有SQL语句抛出异常,FORALL语句就会终止执行。在上面的例子中,第二个UPDATE语句抛出了异常,第三个语句就不会被执行了。

2、使用%BULK_ROWCOUNT属性来计算FORALL语句所影响到的行数

处理SQL数据操作语句时,SQL引擎会隐式地打开一个名为SQL的游标。这个游标的标量属性%FOUND、%ISOPEN、%NOTFOUND和%ROWCOUNT,能够提供最近一次执行的SQL数据操作语句信息。

SQL游标还有一个专门为FORALL设计的复合属性%BULK_ROWCOUNT。这个属性有些像索引表。它的第i个元素保存了第i次的 INSERT或UPDATE或DELETE语句所影响到的行数。如果第i次操作没有行被影响,%BULK_ROWCOUNT(i)就返回零。下面来看一个例子:

DECLARE
  TYPE numlist IS TABLE OF NUMBER;

  depts   numlist := numlist(10, 20, 50);
BEGIN
  FORALL j IN depts.FIRST .. depts.LAST
    UPDATE emp
       SET sal = sal * 1.10
     WHERE deptno = depts(j);
  -- Did the 3rd UPDATE statement affect any rows?
  IF SQL%BULK_ROWCOUNT(3) = 0 THEN ...
END;

FORALL语句和%BULK_ROWCOUNT属性使用同样的下标索引。如果FORALL使用下标索引的范围在5到10的话,那么% BULK_ROWCOUNT的也是。对于插入操作来说,%BULK_ROWCOUNT的结果一般是1,但是对于INSERT ... SELECT这样的结构来说,%BULK_ROWCOUNT的值就有可能大于1。例如,下面的FORALL语句在循环插入数据的过程中,每次插入的行的个数都是不固定的,%BULK_ROWCOUNT可以记录每次插入的行数:

SET SERVEROUTPUT ON;

DECLARE
  TYPE num_tab IS TABLE OF NUMBER;

  deptnums   num_tab;
BEGIN
  SELECT deptno
  BULK COLLECT INTO deptnums
    FROM dept;

  FORALL i IN 1 .. deptnums.COUNT
    INSERT INTO emp_by_dept
      SELECT empno, deptno
        FROM emp
       WHERE deptno = deptnums(i);

  FOR i IN 1 .. deptnums.COUNT LOOP
    -- Count how many rows were inserted for each department; that is,
    -- how many employees are in each department.
    DBMS_OUTPUT.put_line(   'Dept '
                         || deptnums(i)
                         || ': inserted '
                         || SQL%BULK_ROWCOUNT(i)
                         || ' records');
  END LOOP;

  DBMS_OUTPUT.put_line('Total records inserted =' || SQL%ROWCOUNT);
END;
/

我们还可以在批量绑定中使用标量属性%FOUND、%ISOPEN、%NOTFOUND和%ROWCOUNT。例如,%ROWCOUNT会返回所有的SQL语句处理的总行数。

%FOUND和%NOTFOUND只是针对最后一次SQL语句执行的结果。但是,我们可以利用%BULK_ROWCOUNT来推断出每个单独语句的处理结果。当%BULK_ROWCOUNT(i)为零时,%FOUND和%NOTFOUND就分别为FALSE和TRUE。

3、使用%BULK_EXCEPTIONS属性来控制FORALL异常

PL/SQL为FORALL语句提供了一个异常控制机制。这个机制能让使用批量绑定的操作保存异常信息并不中断地执行直至完成操作。

为了让批量绑定在错误发生时还能够继续执行,需要在FORALL语句中添加关键字SAVE EXCEPTIONS,语法如下:

FORALL index IN lower_bound..upper_bound SAVE EXCEPTIONS
{insert_stmt | update_stmt | delete_stmt}

执行时发生的所有的异常信息都会保存在新的游标属性%BULK_EXCEPTIONS中。%BULK_EXCEPTIONS是一个记录类型集合,每个记录有两个域,分别是ERROR_INDEX和ERROR_CODE,前者保存FORALL语句的"循环"索引,后者保存对应的Oracle错误编号。

存放在%BULK_EXCEPTIONS中的值总是与最近一次FORALL语句执行的结果相关,异常的个数存放在%BULK_EXCEPTIONS 的COUNT属性中,%BULK_EXCEPTIONS有效的下标索引范围在1到%BULK_EXCEPTIONS.COUNT之间。

我们不使用关键字SAVE EXCEPTIONS时,如果异常发生,FORALL语句就会停止执行,SQL%BULK_EXCEPTIONS.COUNT的值就是一,SQL% BULK_EXCEPTIONS中只包含一条记录;如果没有异常发生,SQL%BULK_EXCEPTIONS.COUNT的值就是零。下面的例子演示了 %BULK_EXCEPTIONS的一些用法:

DECLARE
  TYPE numlist IS TABLE OF NUMBER;

  num_tab      numlist  := numlist(10, 0, 11, 12, 30, 0, 20, 199, 2, 0, 9, 1);
  ERRORS       NUMBER;
  dml_errors   EXCEPTION;
  PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
  FORALL i IN num_tab.FIRST .. num_tab.LAST SAVE EXCEPTIONS
    DELETE FROM emp
          WHERE sal > 500000 / num_tab(i);
EXCEPTION
  WHEN dml_errors THEN
    ERRORS    := SQL%BULK_EXCEPTIONS.COUNT;
    DBMS_OUTPUT.put_line('Number of errors is ' || ERRORS);

    FOR i IN 1 .. ERRORS LOOP
      DBMS_OUTPUT.put_line(   'Error '
                           || i
                           || ' occurred during '
                           || 'iteration '
                           || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
      DBMS_OUTPUT.put_line(   'Oracle error is '
                           || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
    END LOOP;
END;

例子中,PL/SQL在i等于2、6、10时会抛出预定义异常ZERO_DIVIDE。当批量绑定完成时,SQL% BULK_EXCEPTIONS.COUNT就会返回3,SQL%BULK_EXCEPTIONS的内容就是(2,1476),(6,1476)和 (10,1476)。如果想得到错误消息,我们可以把SQL%BULK_EXCEPTIONS(i).ERROR_CODE传递给错误报告函数 SQLERRM,这样就能得到下面的输出结果:

Number of errors is 3
Error 1 occurred during iteration 2
Oracle error is ORA-01476: divisor is equal to zero
Error 2 occurred during iteration 6
Oracle error is ORA-01476: divisor is equal to zero
Error 3 occurred during iteration 10
Oracle error is ORA-01476: divisor is equal to zero
分享到:
评论

相关推荐

    PL/SQL经典介绍

    第五章 PL-SQL集合与记录(1) 第六章 PL-SQL集合与记录(2) 第七章 PL-SQL与Oracle间交互 第八章 控制PL-SQL错误 第九章 PL-SQL子程序 第十章 PL-SQL包 第十一章 PL-SQL对象类型 第十二章 本地动态SQL 第十三章 PL...

    Oracle PL/SQL程序设计(第5版)(下册)第二部分

    第5部分 构造PL/SQL应用程序 第17章 过程、函数与参数 543 第18章 包 593 第19章 触发器 626 第20章 管理PL/SQL代码 685 第21章 PL/SQL的性能优化 753 第22章 I/O操作和PL/SQL 843 第6部分 高级PL/SQL主题 第23章 ...

    Oracle PL SQL程序设计 上 第五版(代码示例)

    《oracle pl/sql程序设计(第5版)》基于oracle数据库11g,从pl/sql编程、pl/sql程序结构、pl/sql程序数据、pl/sql中的sql、pl/sql应用构建、高级pl/sql主题6个方面详细系统地讨论了pl/sql以及如何有效地使用它。...

    Oracle PL/SQL程序设计(第5版)(上下册)

    - **集合与记录**:学习如何使用PL/SQL中的集合和记录类型,这些结构可以帮助更高效地处理数据集。 - **游标**:介绍游标的使用方法,包括隐式游标和显式游标,以及如何利用游标遍历查询结果。 #### 四、PL/SQL中的...

    Oracle 11g SQL和PL SQL从入门到精通 pdf格式电子书 下载(二)

     第5章 sql和pl/sql综述  第6章 简单查询  第7章 sql单行函数  第8章 操纵数据  第9章 复杂查询  第10章 管理常用对象 第三部分 pl/sql  第11章 pl/sql基础  第12章 访问oracle  第13章 编写控制结构  第...

    Oracle 11g SQL和PL SQL从入门到精通 pdf格式电子书 下载(一)

     第5章 sql和pl/sql综述  第6章 简单查询  第7章 sql单行函数  第8章 操纵数据  第9章 复杂查询  第10章 管理常用对象 第三部分 pl/sql  第11章 pl/sql基础  第12章 访问oracle  第13章 编写控制结构  第...

    ORALCE PL/SQL程序设计第五版

    《ORACLE PL/SQL程序设计第五版》是深入学习Oracle数据库编程的重要参考资料,它涵盖了PL/SQL语言的基础到高级概念,旨在帮助读者迅速提升在Oracle环境中编写高效、可靠的存储过程和函数的能力。这本书分为上、下两...

    Oracle PL/SQL程序设计(第5版)(下册) 第一部分

    第5部分 构造PL/SQL应用程序 第17章 过程、函数与参数 543 第18章 包 593 第19章 触发器 626 第20章 管理PL/SQL代码 685 第21章 PL/SQL的性能优化 753 第22章 I/O操作和PL/SQL 843 第6部分 高级PL/SQL主题 第23章 ...

    ORACLE PL/SQL 程序设计(第五版)

    《ORACLE PL/SQL 程序设计(第五版)》是Oracle数据库开发领域的一本经典教程,专注于讲解如何使用PL/SQL这一强大的过程化语言进行数据库编程。这本书旨在帮助读者掌握PL/SQL的基本概念、语法和高级特性,以便在实际...

    pl/sql集合相关例子

    PL/SQL提供了多种类型的集合来满足不同的编程需求,主要包括数组(Arrays)、记录(Records)、表(Tables)以及对象(Objects)。本文将重点介绍数组、记录、表和对象的使用方法及相关函数。 #### 二、数组...

    Oracle 11g SQL和PL SQL从入门到精通〖送源代码〗

     第5章SQL和PL/SQL综述  第6章简单查询  第7章SQL单行函数  第8章操纵数据  第9章复杂查询  第10章管理常用对象 第三部分PL/SQL  第11章PL/SQL基础  第12章访问Oracle  第13章编写控制结构  第14章使用...

    Pl/SQL programming 超全经典官方多本书籍资料

    这是Steven Feuerstein的经典著作《Oracle PL/SQL Programming》的第五版,被誉为PL/SQL编程的权威指南。书中不仅讲解了基本概念,还包括了许多高级主题,如并发控制、事务管理、错误处理策略、性能调优技巧,以及PL...

    PL/SQL 用户指南与参考

    #### 第五章 PL/SQL集合与记录 - **集合**: - **TABLE**:有序集合,支持重复元素。 - **VARRAY**:变长数组,长度固定但元素可变。 - **记录**: - 定义记录类型,存储不同类型的值。 #### 第六章 PL/SQL与...

    Oracle PL SQL 程序设计 下 第五版

    - Bulk Collect允许高效地将数据库结果集批量收集到PL/SQL集合中,提高性能。 12. **与SQL的交互** - PL/SQL可以直接执行DML(INSERT、UPDATE、DELETE)和DDL(CREATE、ALTER、DROP)语句,也可以使用动态SQL执行...

    oracle pl/sql最佳实践

    利用PL/SQL调试器或第三方工具进行调试,找出程序错误。 通过遵循这些Oracle PL/SQL的最佳实践,您可以构建高效、稳定且易于维护的数据库应用程序。持续学习和实践,不断提升自己的技能,是成为优秀Oracle PL/SQL...

    基本PL/SQL编程

    ### 第一章 PL/SQL程序设计简介 PL/SQL(Procedural Language/Structured Query Language)结合了SQL的查询功能和一种类似C或Pascal的编程语法,使得开发人员能够在数据库环境中编写存储过程、函数、触发器等。PL/...

    Oracle PL_SQL Language Pocket Reference, 5th Edition.2015.pdf

    ### Oracle PL/SQL 语言袖珍参考手册第五版 (2015) 知识点概览 #### 核心内容概述 《Oracle PL/SQL 语言袖珍参考手册》(第五版)由三位专家Steven Feuerstein、Bill Pribyl和Chip Dawes共同编著,是一本专为Oracle...

    PL/SQL学习总结

    PL/SQL 是 Oracle 数据库的一种过程化语言,用于编写数据库应用程序。它结合了 SQL 查询功能和高级编程元素,如变量、流程控制结构和异常处理。本文主要总结了 PL/SQL 的一些基本语句和操作,特别关注在 ORACLE 报表...

Global site tag (gtag.js) - Google Analytics