Chinaunix首页 | 论坛 | 博客
  • 博客访问: 103765820
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类: Oracle

2008-05-01 18:04:13

PL/SQL补疑之Collections and Records

笔记

Wangwj/valen won

Scttsc/cnoug

2007-12-24

参考文档:oracle PLSQL reference 10g r2

Merry Christmas!



废话一段,我喜欢PL/SQL。在参加过的大大小小项目中也喜欢写PL/SQL,虽然有的OOP爱好者不免BS之,说PL/SQL是面向过程,OOP特性太差。我也看《JAVA设计模式》,不过我觉得模式是为了解决问题,而不是模式消除了问题。我举一个例子,某次我在一个项目中,有一个均匀分布的问题,一个List读取1000张单据,往往某个地区会聚集在一起,占有大量处理时间,而其它地区处理不到。Leader最初的解决方案,是对List做排序,改程序大概改了一上午,也觉得不理想。后来,我来改SQL5分钟,问题解决,后来还可以配置分布比例。虽然我用PL/SQL5年多了,仍然有一些盲区,例如记录、集合、对象是用得非常少,所以我特意重新学习并总结,贴出来大家共同进步。

What are PL/SQL Collections and Records?

PL/SQL支持三种类型的Connection

n     Associative arrays 关联数组,也被称为索引表,类似于其它语言中的Hash table。你可以使用任意下标(数字或者字符串型)来查找对象。

n     Nested tables 嵌套表,使用一串连续的数字做下标,它可以存储在表中,用SQL进行操作。

n     Varrays Nested tables类似,但灵活性不如Nested tables。可以在runtime改变大小。

Nested Tables

你可以吧nested table 看着是一种没有宣布长度的一纬数组。你也可以创建多维数组,在nested tables中存储其它的nested tablesNested tables中的行并没有进行特别的排序。那么它和arrays有什么区别呢?

nested table没有宣布长度。Arrays有预定义的长度。Nested table 可以动态增加长度,但是它并不是无限的。

Nested table 可以没有连续的下标,arrays则是连续的。最初,nested table是连续的,但是当你删除元素以后,它就变得离散。

Varrays

Varray有一个最大长度,它的下限是1,然而你可以扩展它的长度。Associative arrays可以帮助你快速的查询某个元素,而不用知道它的具体位置,就像用主键访问一个表一样。因为Associative arrays主要是为临时存储而用的,所以它能用于SQL中,但你可以把它定义在一个package中,从而获得在一个session中的持久性。

Records

Record类似于一行里面的字段,%ROWTYPE属性可以表现一个数据库表里的一行,而不用列出所有字段。

定义Collection类型和宣布Collection变量

一个基本涵盖大部分定义方法的例子如下:

create or replace package pkg_connections is
TYPE Calendar IS VARRAY(
10) OF DATE;
PROCEDURE pro_test_define;
end pkg_connections;

/

create or replace package body pkg_connections is

PROCEDURE pro_test_define IS
TYPE nested_type IS TABLE OF VARCHAR2(
30); --nested table
TYPE varray_type IS VARRAY(
5) OF INTEGER; --varray
TYPE assoc_array_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
--associate array
TYPE assoc_array_str_type IS TABLE OF VARCHAR2(
32) INDEX BY PLS_INTEGER;
TYPE assoc_array_str_type2 IS TABLE OF VARCHAR2(
32) INDEX BY VARCHAR2(64);
v1 nested_type;
v2 varray_type;
v3 assoc_array_num_type;
v4 assoc_array_str_type;
v5 assoc_array_str_type2;
--%TYPE定义 Collections
v6 v1%TYPE;
TYPE EmpList IS TABLE OF company.companynumber%TYPE;
-- Declare a cursor to select a subset of columns.
CURSOR c1 IS SELECT companynumber FROM company;
-- Declare an Array type that can hold information about 10 employees.
-- The element type is a record that contains all the same
-- fields as the company table.
TYPE Senior_Salespeople IS VARRAY(
10) OF company%ROWTYPE;
-- Declare a cursor to select a subset of columns.
CURSOR c2 IS SELECT companynumber, companyid FROM company;
-- Array type that can hold a list of names. The element type
-- is a record that contains the same fields as the cursor
-- (that is, first_name and last_name).
TYPE NameList IS VARRAY(
20) OF c2%ROWTYPE;
--VARRAY of Records
TYPE name_rec IS RECORD ( first_name VARCHAR2(
20), last_name VARCHAR2(25) );
TYPE names IS VARRAY(
250) OF name_rec;
--NOT NULL Constraint on Collection Elements
TYPE EmpList1 IS TABLE OF company.companynumber%TYPE NOT NULL;
-- if assigning NULL raises an error
v_employees EmpList := EmpList(
100, 150, 160, 200);
BEGIN
v1 := nested_type(
'Shipping','Sales','Finance','Payroll');
v2 := varray_type(
1, 2, 3, 4, 5); -- Up to 5 integers
v3(
99) := 10; -- Just start assigning to elements
v3(
7) := 100; -- Subscripts can be any integer values
v4(
42) := 'Smith'; -- Just start assigning to elements
v4(
54) := 'Jones'; -- Subscripts can be any integer values
v5(
'Canada') := 'North America'; -- Just start assigning to elements
v5(
'Greece') := 'Europe'; -- Subscripts can be string value
END;
-nested Table做为存储过程的参数
PROCEDURE pro_test_para (ca IN Calendar) IS
BEGIN
NULL;
END;
end pkg_connections;

/

初始化Collections

初始化以前nested table 或者 varray都是空的,我们使用构造方法来初始化,如下:

Nested Table的的构造器
DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30);
dept_names dnames_tab;
BEGIN
dept_names := dnames_tab('Shipping','Sales','Finance','Payroll');
END;
/

nested table不需要制定大小,你可以在构造方法里面放足够多的元素。

Varray的构造器
DECLARE
-- In the varray, we put an upper limit on the number of elements
TYPE dnames_var IS VARRAY(20) OF VARCHAR2(30);
dept_names dnames_var;
BEGIN
-- Because dnames is declared as VARRAY(20), we can put up to 10
-- elements in the constructor
dept_names := dnames_var('Shipping','Sales','Finance','Payroll');
END;
/

Collection 构造器包含空值处理
DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30);
dept_names dnames_tab;
TYPE dnamesNoNulls_type IS TABLE OF VARCHAR2(30) NOT NULL;
BEGIN
dept_names := dnames_tab('Shipping', NULL,'Finance', NULL);
-- If dept_names was of type dnamesNoNulls_type, we could not include
-- null values in the constructor
END;
/

结合Collection定义和构造器
DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30);
dept_names dnames_tab := dnames_tab('Shipping','Sales','Finance','Payroll');
BEGIN
NULL;
END;
/

Varray 构造器
DECLARE
TYPE dnames_var IS VARRAY(20) OF VARCHAR2(30);
dept_names dnames_var;
BEGIN
IF dept_names IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Before initialization, the varray is null.');
-- varray 为空,我不能检查它的COUNT属性
-- DBMS_OUTPUT.PUT_LINE('It has ' || dept_names.COUNT || ' elements.');
ELSE
DBMS_OUTPUT.PUT_LINE('Before initialization, the varray is not null.');
END IF;
dept_names := dnames_var(); -- initialize empty varray
IF dept_names IS NULL THEN
DBMS_OUTPUT.PUT_LINE('After initialization, the varray is null.');
ELSE
DBMS_OUTPUT.PUT_LINE('After initialization, the varray is not null.');
DBMS_OUTPUT.PUT_LINE('It has ' || dept_names.COUNT || ' elements.');
END IF;
END;
/

Referencing Collection Elements

Collection使用下面的语法定义下标:

collection_name(subscript)

大多数例子里面,下标是Integer型,associative arrays也可以定义String作为下标。

允许的下标范围是:

n     nested tables, 1 .. 2147483647 (PLS_INTEGER类型的上限)

n     varrays,1 .. size_limit,但是 size_limit也不要超过2147483647

n     associative arrays,用numberkey的时候 -2147483648 to 2147483647

n     associative arrays,用StringKey的时候,取决于Key的长度和字符集。

Assigning Collections

可以通过INSERT, UPDATE, FETCH, 或者 SELECT,分派语句,子程序调用来分派collection,语法如下:

        collection_name(subscript) := expression;

也可以使用SET, MULTISET UNION, MULTISET INTERSECT, MULTISET EXCEPT
分派collection可能会导致异常,由于:

n     下标为空,或者右值不可变,将会触发VALUE_ERROR

n     下标引用了一个未初始化对象,将触发SUBSCRIPT_BEYOND_COUNT
n     collection 如果是atomically null, 触发COLLECTION_IS_NULL
分派不同类型,引起的异常例子

DECLARE
TYPE last_name_typ IS VARRAY(3) OF VARCHAR2(64);
TYPE surname_typ IS VARRAY(3) OF VARCHAR2(64);
-- 同样的类型
group1 last_name_typ := last_name_typ('Jones','Wong','Marceau');
group2 last_name_typ := last_name_typ('Klein','Patsos','Singh');
-- 类似的定义不同的类型
group3 surname_typ := surname_typ('Trevisi','Macleod','Marquez');
BEGIN
-- 可以
group1 := group2;
-- 不允许,会报错
-- group3 := group2; -- raises an error
END;
/

Nested table 分派空值

DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30);
dept_names dnames_tab := dnames_tab('Shipping','Sales','Finance','Payroll');
-- 没有初始化 ("atomically null").
empty_set dnames_tab;
BEGIN
-- 首先它不为空.
if dept_names IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE('OK, at first dept_names is not null.');
END IF;
-- 分配一个空值给它.
dept_names := empty_set;
-- 它为空了.

if dept_names IS NULL THEN
DBMS_OUTPUT.PUT_LINE('OK, now dept_names has become null.');
END IF;
-- We must use another constructor to give it some values.
dept_names := dnames_tab('Shipping','Sales','Finance','Payroll');
END;
/

分派Nested Tables 通过 Set 操作,交集,剪集,合并等
DECLARE
TYPE nested_typ IS TABLE OF NUMBER;
nt1 nested_typ := nested_typ(1,2,3);
nt2 nested_typ := nested_typ(3,2,1);
nt3 nested_typ := nested_typ(2,3,1,3);
nt4 nested_typ := nested_typ(1,2,4);
answer nested_typ;
PROCEDURE print_nested_table(the_nt nested_typ) IS
output VARCHAR2(128);
BEGIN
IF the_nt IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Results: ');
RETURN;
END IF;
IF the_nt.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('Results: empty set');
RETURN;
END IF;
FOR i IN the_nt.FIRST .. the_nt.LAST
LOOP

output := output || the_nt(i) || ' ';
END LOOP;

DBMS_OUTPUT.PUT_LINE('Results: ' || output);
END;
BEGIN
answer := nt1 MULTISET UNION nt4; -- (1,2,3,1,2,4)
print_nested_table(answer);
answer := nt1 MULTISET UNION nt3; -- (1,2,3,2,3,1,3)
print_nested_table(answer);
answer := nt1 MULTISET UNION DISTINCT nt3; -- (1,2,3)
print_nested_table(answer);
answer := nt2 MULTISET INTERSECT nt3; -- (3,2,1)
print_nested_table(answer);
answer := nt2 MULTISET INTERSECT DISTINCT nt3; -- (3,2,1)
print_nested_table(answer);
answer := SET(nt3); -- (2,3,1)
print_nested_table(answer);
answer := nt3 MULTISET EXCEPT nt2; -- (3)
print_nested_table(answer);
answer := nt3 MULTISET EXCEPT DISTINCT nt2; -- ()
print_nested_table(answer);
END;
/

VARRAYs 分配复杂数据类型的值
DECLARE
TYPE emp_name_rec is RECORD (
firstname employees.first_name%TYPE,
lastname employees.last_name%TYPE,
hiredate employees.hire_date%TYPE
);
-- Array type that can hold information 10 employees
TYPE EmpList_arr IS VARRAY(10) OF emp_name_rec;
SeniorSalespeople EmpList_arr;
-- Declare a cursor to select a subset of columns.
CURSOR c1 IS SELECT first_name, last_name, hire_date FROM employees;
Type NameSet IS TABLE OF c1%ROWTYPE;
SeniorTen NameSet;
EndCounter NUMBER := 10;
BEGIN
SeniorSalespeople := EmpList_arr();
SELECT first_name, last_name, hire_date BULK COLLECT INTO SeniorTen FROM
employees WHERE job_id = 'SA_REP' ORDER BY hire_date;
IF SeniorTen.LAST > 0 THEN
IF SeniorTen.LAST < 10 THEN EndCounter := SeniorTen.LAST;
END IF;
FOR i in 1..EndCounter LOOP
SeniorSalespeople.EXTEND(1);
SeniorSalespeople(i) := SeniorTen(i);
DBMS_OUTPUT.PUT_LINE(SeniorSalespeople(i).lastname || ', '
|| SeniorSalespeople(i).firstname || ', ' ||
SeniorSalespeople(i).hiredate);
END LOOP;

END IF;
END;
/

分配值给Tables 通过复杂数据类型
DECLARE
TYPE emp_name_rec is RECORD (
firstname employees.first_name%TYPE,
lastname employees.last_name%TYPE,
hiredate employees.hire_date%TYPE
);
-- Table type that can hold information about employees
TYPE EmpList_tab IS TABLE OF emp_name_rec;
SeniorSalespeople EmpList_tab;
-- Declare a cursor to select a subset of columns.
CURSOR c1 IS SELECT first_name, last_name, hire_date FROM employees;
EndCounter NUMBER := 10;
TYPE EmpCurTyp IS REF CURSOR;
emp_cv EmpCurTyp;
BEGIN
OPEN emp_cv FOR SELECT first_name, last_name, hire_date FROM employees

WHERE job_id = 'SA_REP' ORDER BY hire_date;
FETCH emp_cv BULK COLLECT INTO SeniorSalespeople;
CLOSE emp_cv;
-- for this example, display a maximum of ten employees
IF SeniorSalespeople.LAST > 0 THEN
IF SeniorSalespeople.LAST < 10 THEN EndCounter := SeniorSalespeople.LAST;
END IF;
FOR i in 1..EndCounter LOOP
DBMS_OUTPUT.PUT_LINE(SeniorSalespeople(i).lastname || ', '
|| SeniorSalespeople(i).firstname || ', ' || SeniorSalespeople(i).hiredate);
END LOOP;

END IF;
END;
/

比较 Collections

你可以检查collection是否为空,但诸如比较greater 或者 less是不行的。同样,它也不能出现在DISTINCT, GROUP BY, 或者 ORDER BY

检查Collection Is Null
DECLARE
TYPE emp_name_rec is RECORD (
firstname employees.first_name%TYPE,
lastname employees.last_name%TYPE,
hiredate employees.hire_date%TYPE
);
TYPE staff IS TABLE OF emp_name_rec;
members staff;
BEGIN
-- 返回 TRUE因为我们没有使用constructor.
IF members IS NULL THEN
DBMS_OUTPUT.PUT_LINE('NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('Not NULL');
END IF;
END;
/

比较两个Nested Tables
DECLARE
TYPE dnames_tab IS TABLE OF VARCHAR2(30);
dept_names1 dnames_tab := dnames_tab('Shipping','Sales','Finance','Payroll');
dept_names2 dnames_tab := dnames_tab('Sales','Finance','Shipping','Payroll');
dept_names3 dnames_tab := dnames_tab('Sales','Finance','Payroll');
BEGIN
-- 我们能使用 = 或者 !=, 但不能是 < 或者 >.
-- 返回true,虽然他们的顺序不一样.
IF dept_names1 = dept_names2 THEN
DBMS_OUTPUT.PUT_LINE('dept_names1 and dept_names2 have the same members.');
END IF;
IF dept_names2 != dept_names3 THEN
DBMS_OUTPUT.PUT_LINE('dept_names2 and dept_names3 have different members.');
END IF;
END;
/

比较 Nested Tables 通过 Set 操作
DECLARE
TYPE nested_typ IS TABLE OF NUMBER;
nt1 nested_typ := nested_typ(1,2,3);
nt2 nested_typ := nested_typ(3,2,1);
nt3 nested_typ := nested_typ(2,3,1,3);
nt4 nested_typ := nested_typ(1,2,4);
answer BOOLEAN;
howmany NUMBER;
PROCEDURE testify(truth BOOLEAN DEFAULT NULL, quantity NUMBER DEFAULT NULL) IS
BEGIN
IF truth IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(CASE truth WHEN TRUE THEN 'True' WHEN FALSE THEN
'False' END);
END IF;
IF quantity IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(quantity);
END IF;
END;
BEGIN
answer := nt1 IN (nt2,nt3,nt4); -- true
testify(truth => answer);
answer := nt1 SUBMULTISET OF nt3; -- true, all elements match
testify(truth => answer);
answer := nt1 NOT SUBMULTISET OF nt4; -- also true
testify(truth => answer);
howmany := CARDINALITY(nt3); -- number of elements in nt3
testify(quantity => howmany);
howmany := CARDINALITY(SET(nt3)); -- number of distinct elements
testify(quantity => howmany);
answer := 4 MEMBER OF nt1; -- false, no element matches
testify(truth => answer);
answer := nt3 IS A SET; -- false, nt3 has duplicates
testify(truth => answer);
answer := nt3 IS NOT A SET; -- true, nt3 has duplicates
testify(truth => answer);
answer := nt1 IS EMPTY; -- false, nt1 has some members
testify(truth => answer);

END;

/

使用多级Collections

多级 VARRAY
DECLARE
TYPE t1 IS VARRAY(10) OF INTEGER;
TYPE nt1 IS VARRAY(10) OF t1; -- multilevel varray type
va t1 := t1(2,3,5);
-- initialize multilevel varray
nva nt1 := nt1(va, t1(55,6,73), t1(2,4), va);
i INTEGER;
va1 t1;
BEGIN
-- 多级访问
i := nva(2)(3); -- 获得值 73
DBMS_OUTPUT.PUT_LINE('I = ' || i);
-- 添加一个新的 varray 元素给 nva,不扩展会报ORA-06533: 下标超出数量
nva.EXTEND;
-- replace inner varray elements
nva(5) := t1(56, 32);
nva(4) := t1(45,43,67,43345);
-- replace an inner integer element
nva(4)(4) := 1; -- replaces 43345 with 1
-- add a new element to the 4th varray element
-- and store integer 89 into it.
nva(4).EXTEND;
nva(4)(5) := 89;
END;
/

多级Nested Table
DECLARE
TYPE tb1 IS TABLE OF VARCHAR2(20);
TYPE Ntb1 IS TABLE OF tb1; -- table of table elements
TYPE Tv1 IS VARRAY(10) OF INTEGER;
TYPE ntb2 IS TABLE OF tv1; -- table of varray elements
vtb1 tb1 := tb1('one', 'three');
vntb1 ntb1 := ntb1(vtb1);
vntb2 ntb2 := ntb2(tv1(3,5), tv1(5,7,3)); -- table of varray elements
BEGIN
vntb1.EXTEND;
vntb1(2) := vntb1(1);
-- delete the first element in vntb1
vntb1.DELETE(1);

-- delete the first string from the second table in the nested table
vntb1(2).DELETE(1);
END;
/

多级Associative Array
DECLARE
TYPE tb1 IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
-- the following is index-by table of index-by tables
TYPE ntb1 IS TABLE OF tb1 INDEX BY PLS_INTEGER;
TYPE va1 IS VARRAY(10) OF VARCHAR2(20);
-- the following is index-by table of varray elements
TYPE ntb2 IS TABLE OF va1 INDEX BY PLS_INTEGER;
v1 va1 := va1('hello', 'world');
v2 ntb1;
v3 ntb2;
v4 tb1;
v5 tb1; -- empty table
BEGIN
v4(1) := 34;
v4(2) := 46456;
v4(456) := 343;
v2(23) := v4;
v3(34) := va1(33, 456, 656, 343);
-- assign an empty table to v2(35) and try again
v2(35) := v5;
v2(35)(2) := 78; -- it works now
END;

/

原文:http://valen.blog.ccidnet.com/blog-htm-do-showone-uid-51502-type-blog-itemid-227040.html

阅读(604) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~