PL/SQL — 集合及常用方法

时间:2022-01-06 14:16:22

PL/SQL中提供了常用的三种集合联合数组、嵌套表、变长数组,而对于这几个集合类型中元素的操作,PL/SQL提供了相应的函数或过程来操纵数组中的元素或下标。这些函数或过程称为集合方法。一个集合方法就是一个内置于集合中并且能够操作集合的函数或过程,可以通过点标志来调用。本文主要描述如何操作这些方法。

一、集合类型提供的方法与调用方式
1、集合的方法与调用方式
EXISTS
函数EXISTS(n)在第n个元素存在的情况下会返回TRUE,否则返回FALSE。
通常使用EXISTS和DELETE来维护嵌套表。其中EXISTS还可以防止引用不存在的元素,避免发生异常。当下标越界时,EXISTS会返回FALSE,而不是抛出SUBSCRIPT_OUTSIDE_LIMIT异常。

COUNT
COUNT能够返回集合所包含的元素个数,对于大小不确定的情形则COUNT非常有用。
可以在任何可以使用整数表达式的地方使用COUNT函数,如作为for循环的上限。计算元素个数时,被删除的元素不会被count所统计。对于变长数组来说,COUNT值与LAST值恒等。对于嵌套表来说,正常情况下COUNT值会和LAST值相等。但是,当我们从嵌套表中间删除一个元素,COUNT值就会比LAST值小。
  
LIMIT
用于检测集合的最大容量。
由于嵌套表和关联数组都没有上界限制,所以LIMIT总会返回NULL。对于变长数组,LIMIT会返回它所能容纳元素的个数最大值,该值是在变长数组声明时指定的,并可用TRIM和EXTEND方法调整。
 
FIRST,LAST
FIRST和LAST会返回集合中第一个和最后一个元素在集合中的下标索引值。
对于使用VARCHAR2类型作为键的关联数组来说,会分别返回最低和最高的键值;键值的高低顺序是基于字符串中字符的二进制值。但是,如果初始化参数NLS_COMP被设置成ANSI的话,键值的高低顺序就受初始化参数NLS_SORT所影响了。
空集合的FIRST和LAST方法总是返回NULL。只有一个元素的集合,FIRST和LAST会返回相同的索引值。
对于变长数组,FIRST恒等于1,LAST恒等于COUNT。
对于嵌套表,FIRST通常返回1,如果删除第一个元素,则FIRST的值大于1,如果删除中间的一个元素,此时LAST就会比COUNT大。
在遍历元素时,FIRST和LAST都会忽略被删除的元素。

PRIOR,NEXT
PRIOR(n)会返回集合中索引为n的元素的前驱索引值;NEXT(n)会返回集合中索引为n的元素的后继索引值。如果n没有前驱或后继,PRIOR(n)或NEXT(n)就会返回NULL。
对于使用VARCHAR2作为键的关联数组来说,它们会分别返回最低和最高的键值;键值的高低顺序是基于字符串中字符的二进制值。
PRIOR和NEXT不会从集合的一端到达集合的另一端,即最末尾元素的的next不会指向集合中的first。
在遍历元素时,PRIOR和NEXT都会忽略被删除的元素,即如果prior(3)之前的2被删除则指向1,如果1也被删除则返回null。

EXTEND
用于扩大嵌套表或变长数组的容量,该方法不能用于联合数组。
EXTEND有三种形式
  EXTEND 在集合末端添加一个空元素
  EXTEND(n) 在集合末端添加n个空元素
  EXTEND(n,i) 把第i个元素拷贝n份,并添加到集合的末端
对嵌套表或变长数组添加了NOT NULL约束之后,不能使用EXTEND的前两种形式。
EXTEND操作的是集合内部大小,其中也包括被删除的元素。所以,在计算元素个数的时候,EXTEND也会把被删除的元素考虑在内。
对于使用DELETE方法操作的元素,PL/SQL会保留其占位符,后续可以重新利用。
 
TRIM
从集合的末尾删除一个(TRIM)或指定数量TRIM(n)的元素,PL/SQL对TRIM掉的元素不再保留占位符。如果n值过大的话,TRIM(n)就会抛出SUBSCRIPT_BEYOND_COUNT异常。
通常,不要同时使用TRIM和DELETE方法。可把嵌套表当作定长数组,只使用DELETE方法,或是当作栈,只对它使用TRIM和EXTEND方法。
 
DELETE
删除集合中的所有或指定范围的元素。
  DELETE 删除集合中所有元素 。
  DELETE(n) 从以数字作主键的关联数组或者嵌套表中删除第n个元素。如果关联数组有一个字符串键,对应该键值的元素就会被删除。如果n为空,DELETE(n)不会做任何事情。
  DELETE(m,n) 从关联数组或嵌套表中,把索引范围m到n的所有元素删除。如果m值大于n或是m和n中有一个为空,那么DELETE(m,n)就不做任何事。
PL/SQL会为使用DELETE方式删除的元素保留一个占位符,后续可以重新为被删除的元素赋值。
注,不能使用delete方式删除变长数组中的元素。
  
调用方式:
  collection_name.method_name[(parameters)]

2、集合方法注意事项
    1)集合的方法不能在SQL语句中使用。
    2)EXTEND和TRIM方法不能用于关联数组。
    3)EXISTS,COUNT,LIMIT,FIRST,LAST,PRIOR和NEXT是函数;EXTEND,TRIM和DELETE是过程。
    4)EXISTS,PRIOR,NEXT,TRIM,EXTEND和DELETE对应的参数是集合的下标索引,通常是整数,但对于关联数组来说也可能是字符串。
    5)只有EXISTS能用于空集合,如果在空集合上调用其它方法,PL/SQL就会抛出异常COLLECTION_IS_NULL。

二、各个方法综合演示
-->示例1
DECLARE
  output   VARCHAR2( 300 );
  TYPE index_by_type IS TABLE OF VARCHAR2( 10 ) INDEX BY BINARY_INTEGER;
  index_by_table  index_by_type;
 
  TYPE nested_type IS TABLE OF NUMBER;
  //在声明块对嵌套表进行初始化并赋值
  nested_table nested_type := nested_type( 10,20,30,40 ,50 ,60 ,70,80 ,90,100 );
BEGIN
  FOR i IN 1 .. 10
    LOOP
        index_by_table( i ) := 'Value_' || i;
  END LOOP;
 
  DBMS_OUTPUT.put_line( '------------- Before deleted ----------------' );
 
//使用了first,last,作循环计数器上下标输出当前联合数组的所有元素
  FOR i IN index_by_table.FIRST .. index_by_table.LAST
  LOOP
    output  := output || NVL( TO_CHAR( index_by_table( i ) ), 'NULL' ) || ' ';
  END LOOP;
 
  DBMS_OUTPUT.put_line( 'Element of Index_by_table are: ' || output );
  output  := '';
 
//使用了count,作循环计数器上下标输出当前嵌套表的所有元素
  FOR i IN 1 .. nested_table.COUNT    
  LOOP
  output  := output || NVL( TO_CHAR( nested_table( i ) ), 'NULL' ) || ' ';
  END LOOP;
 
  DBMS_OUTPUT.put_line( 'Element of nested_table are: ' || output );
 
//EXISTS函数判断联合数组中的第3个元素是否存在
  IF index_by_table.EXISTS( 3 ) THEN 
    DBMS_OUTPUT.put_line( 'index_by_table(3) exists and the value is ' || index_by_table( 3 ) );
  END IF;
 
//delete 10th element from a collection
  nested_table.delete( 10 );
//delete elements 1 through 3 from a collection
  nested_table.delete( 1, 3 );
  index_by_table.delete( 10 );
  DBMS_OUTPUT.put_line( 'nested_table.COUNT = ' || nested_table.COUNT );
  DBMS_OUTPUT.put_line( 'index_by_table.COUNT = ' || index_by_table.COUNT );
  DBMS_OUTPUT.put_line( 'nested_table.FIRST = ' || nested_table.FIRST );
  DBMS_OUTPUT.put_line( 'nested_table.LAST = ' || nested_table.LAST );
  DBMS_OUTPUT.put_line( 'index_by_table.FIRST = ' || index_by_table.FIRST );
  DBMS_OUTPUT.put_line( 'index_by_table.LAST = ' || index_by_table.LAST );
  DBMS_OUTPUT.put_line( 'nested_table.PRIOR(2) = ' || nested_table.PRIOR( 2 ) );
  DBMS_OUTPUT.put_line( 'nested_table.NEXT(2) = ' || nested_table.NEXT( 2 ) );
  DBMS_OUTPUT.put_line( 'index_by_table.PRIOR(2) = ' || index_by_table.PRIOR( 2 ) );
  DBMS_OUTPUT.put_line( 'index_by_table.NEXT(2) = ' || index_by_table.NEXT( 2 ) );
//Trim last two elements
  nested_table.TRIM( 2 );
//Trim last element
  nested_table.TRIM;
  DBMS_OUTPUT.put_line( 'nested_table.LAST = ' || nested_table.LAST );
  DBMS_OUTPUT.put_line( '------------------- After deleted -------------------' );

//输出删除元素后联合数组的所有剩余元素
 output:='';
  FOR i IN index_by_table.FIRST .. index_by_table.LAST
  LOOP
    output  := output || NVL( TO_CHAR( index_by_table( i ) ), 'NULL' ) || ' ';
  END LOOP;
 
  DBMS_OUTPUT.put_line( 'Element of Index_by_table are: ' || output );
  output  := '';
 
//输出删除元素后嵌套表的所有剩余元素
 output:='';
  FOR i IN nested_table.FIRST .. nested_table.LAST 
  LOOP
    output  := output || NVL( TO_CHAR( nested_table( i ) ), 'NULL' ) || ' ';
  END LOOP;
 
  DBMS_OUTPUT.put_line( 'Element of nested_table are: ' || output );
END;
 
--------------------------- Before deleted -----------------------------------------
Element of Index_by_table are: Value_1 Value_2 Value_3 Value_4 Value_5 Value_6 Value_7 Value_8 Value_9 Value_10
Element of nested_table are: 10 20 30 40 50 60 70 80 90 100
index_by_table(3) exists and the value is Value_3
nested_table.COUNT = 6  -->嵌套表使用了两次delete,分别是删除最后一个元素和删除第1到第3个元素,因此嵌套表的count输出为6
index_by_table.COUNT = 9 -->联合数组中删除了最后的一个元素,因此联合数组的count输出为9
nested_table.FIRST = 4  -->嵌套表删除了第1到第3个元素,因此其first变成4
nested_table.LAST = 9  -->嵌套表删除了最后一个元素,因此last变成9
index_by_table.FIRST = 1
index_by_table.LAST = 9
nested_table.PRIOR(2) = -->嵌套表的PRIOR(2),第2个元素的前一个(下标为1),由于1-3都被删除,且1之前没有任何元素,故为NULL
nested_table.NEXT(2) = 4 -->嵌套表2之后元素的下标,原本应该是3,由于3被删除,因此3被忽略,返回4
index_by_table.PRIOR(2) = 1
index_by_table.NEXT(2) = 3
nested_table.LAST = 7  -->nested_table.TRIM(2)与nested_table.TRIM总共删除了3个元素及占位符,故LAST为7。
--------------------------- After deleted -----------------------------------------
Element of Index_by_table are: Value_1 Value_2 Value_3 Value_4 Value_5 Value_6 Value_7 Value_8 Value_9
Element of nested_table are: 40 50 60 70
 
PL/SQL procedure successfully completed.
----------------------------------------------------------------------------------------------------------------------------  
-->示例2
DECLARE
  TYPE varray_type IS VARRAY(10) OF NUMBER;
  varray varray_type := varray_type(1, 2, 3, 4, 5, 6);
  PROCEDURE print_numlist( the_list varray_type ) IS output  VARCHAR2( 128 );
BEGIN
    FOR i IN the_list.FIRST .. the_list.LAST
  LOOP
  output  := output || NVL( TO_CHAR( the_list( i ) ), 'NULL' ) || ' ';
  END LOOP;
 
  DBMS_OUTPUT.put_line( output );
END;

BEGIN
  print_numlist( varray );
  DBMS_OUTPUT.put_line( 'varray.COUNT = ' || varray.COUNT );
  DBMS_OUTPUT.put_line( 'varray.LIMIT = ' || varray.LIMIT );
  DBMS_OUTPUT.put_line( 'varray.FIRST = ' || varray.FIRST );
  DBMS_OUTPUT.put_line( 'varray.LAST = ' || varray.LAST );
  DBMS_OUTPUT.put_line( 'The maximum number you can use with ' || 'varray.EXTEND() is ' || ( varray.LIMIT - varray.COUNT ) );
  varray.EXTEND( 2, 4 ); //将第4个元素的值复制2份,追加到集合尾部
  DBMS_OUTPUT.put_line( 'varray.LAST = ' || varray.LAST );
  DBMS_OUTPUT.put_line( 'varray(' || varray.LAST || ') = ' || varray( varray.LAST ) );
  print_numlist( varray );
  -- Trim last two elements
  varray.TRIM( 2 );
  DBMS_OUTPUT.put_line( 'varray.LAST = ' || varray.LAST );
END;
 
1 2 3 4 5 6   -->输出varray中的所有元素
varray.COUNT = 6
varray.LIMIT = 10  -->limit方法得到变长数组的最大容量
varray.FIRST = 1
varray.LAST = 6
The maximum number you can use with varray.EXTEND() is 4  -->得到可以extend的容量,即还可以保存4个元素
varray.LAST = 8  --> extend之后last的下标值为8
varray(8) = 4  -->第8个元素的值则为4
1 2 3 4 5 6 4 4  -->输出varray中的所有元素
varray.LAST = 6  -->由于使用了varray.TRIM( 2 ),所以last又变成了6
 
PL/SQL procedure successfully completed.