Oracle PL/SQL变得更快、更易于使用,特性集也更加丰富了。Oracle数据库10g通过一系列有益的改进,继续保持了PL/SQL在速度、简单易用性和特性扩展方面的传统优势,这些改进包括:
大大提高了执行速度,这要归功于透明的性能改进,其中包括一个新的优化编译器、更优的集成化本地编译功能,以及帮助解决数字运算应用程序问题的新的数据类型。
FORALL语句更加灵活、更加有用。例如:FORALL现在支持非连续索引。
正则表达式以三个新函数(REGEXP_INSTR、REGEXP_REPLACE和REGEXP_SUBSTR)和用于比较的REGEXP_LIKE运算符的形式用于PL/SQL语言中。(要获得更多信息,参见本期杂志中Jonathan Gennick撰写的《一流的表达式》一文。)
集合得到了改进,包括比较集合是否相同、支持对嵌套表进行集合运算等。
Oracle数据库10g使PL/SQL继续保持其作为Oracle数据库的最高效、最富生产力的编程语言的地位。其在性能方面的大大提升,以及对IEEE算法和正则表达式的支持,如今完全开启了将PL/SQL作为首选语言的新的功能领域。
作为介绍Oracle数据库10g中的PL/SQL系列文章的第一篇,本文将对Oracle数据库10g中与集合相关的一些改进进行探讨。
比较集合
20世纪90年代末期,当我首次对开发人员进行PL/SQL培训的时候,很少有人使用(甚至知道)包(package),而现在,很多人(包括我自己在内)都认为它是任何设计精良的PL/SQL应用程序的基础。如今,打包软件得到了广泛使用。 目前,非常重要但未得到充分利用的PL/SQL特性的前沿领域似乎就是集合的使用了。
集合是Oracle中的数组,集合是一维列表。早在Oracle7中,就已经用到了集合(那时,称作“PL/SQL表”),但其功能及性能却很有限。不过,Oracle后来的各个版本都对集合进行了改进。在Oracle数据库10g中,这些数据结构对于几乎所有的复杂PL/SQL应用程序项目来说都是强有力的、快速的和必不可少的。
Oracle数据库10g对集合的一个关键性改进是能够比较两个集合内容的相同之处(和不同之处)。在Oracle数据库10g之前,你也可以对两个集合进行比较,但必须为此编写一个函数。编写这样的程序时需要考虑数个复杂因素,包括:
必须为正在使用的各个集合类型分别编写程序。即使两个集合中的数据类型相同,但如果它们不是基于完全相同的类型定义的,你都需要使用不同的函数来进行比较。
必须对表的内容进行逐行比较,这就意味着必须进行“全集合扫描”。完成这一任务的代码不是很复杂。但是,这一代码冗长乏味,易于出错,特别是比较诸如记录、对象或者XMLType等复杂数据类型的集合时更是如此。
你必须决定如何处理NULL。如果两行都包含NULL,那么它们相同吗?Oracle认为:“它们既不是相同,也不是不相同,”但是,你的判断可能会与此不同,你必须编写代码来处理这个问题。
这种复杂性导致你不愿意经常编写这类程序,你甚至会在应用程序中回避编写这类程序。
在Oracle数据库10g中,collcompare.sql文件包含了这类程序的一个示例,该程序是为基于employee表的记录集合而编写的。
假设我在collcompare.sql脚本中安装了emp_coll_pkg.equal函数。我可以按照如下方式来使用它:
DECLARE
dbas emp_coll_pkg.employee_tt;
developers emp_coll_pkg.employee_tt;
BEGIN
populate_lists (dbas, developers);
IF emp_coll_pkg.equal (dbas, developers)
THEN
DBMS_OUTPUT.PUT_LINE (
'Likely a very small IT organization!');
END IF;
END;
这段代码简单明了、可读性好。(你不用再经历编写此类函数的痛苦过程多好啊!)为了让你工作更轻松,Oracle数据库10g现在允许你对两个嵌套表进行“原始”比较(native compare)了。换句话说,你不必再编写任何特定于集合的比较逻辑,而可以直接进行比较,如下所示:
DECLARE
dbas emp_coll_pkg.employee_tt;
developers emp_coll_pkg.employee_tt;
BEGIN
populate_lists (dbas, developers);
IF dbas = developers
THEN
DBMS_OUTPUT.PUT_LINE (
'Likely a very small IT organization!');
END IF;
END;
在这种情况下,集合比较仅适用于嵌套表。换句话说,你还不能直接比较两个关联数组(过去称作"索引表")或者变长数组的内容。希望Oracle数据库的下一版本会增加关联数组和变长数组的比较功能。
集合理论与Multiset Union运算
SQL语言很久以前就提供了将集合运算(UNION、INTERSECT和MINUS)用于查询结果集的功能。如今在Oracle数据库10g中,你可以对PL/SQL程序中的嵌套表(且仅限于嵌套表)和在关系表中声明为列的嵌套表使用上述功能强大的高级运算符。
现在我们从UNION开始,看看这样做所需的一些语法。
首先,我创建一个模式级别的嵌套表类型:
CREATE OR REPLACE TYPE strings_nt
IS TABLE OF VARCHAR2(100);
/
然后,我定义一个包,在该包中,我创建并填充两个此种类型的嵌套表,每个嵌套表都包含一些我和我父亲最喜欢的东西:
CREATE OR REPLACE PACKAGE favorites_pkg
IS
my_favorites
strings_nt
:= strings_nt ('CHOCOLATE'
, 'BRUSSEL SPROUTS'
, 'SPIDER ROLL'
);
dad_favorites
strings_nt
:= strings_nt ('PICKLED HERRING
, 'POTATOES'
, 'PASTRAMI'
, 'CHOCOLATE'
);
PROCEDURE show_favorites (
title_in
IN
VARCHAR2
, favs_in
IN
strings_nt
);
END;
/
在该包中,我还创建了一个用于显示strings_nt 嵌套表内容的过程。下面很快就会用到它。
通过在任意程序外的包中定义这些集合,这些集合在我的对话期间会一直保持不变(保持其状态和值),直到我将更改或删除它们为止。这就是说,现在我可以在包外编写程序来对这些集合的内容进行操作了。
注意,出于介绍集合功能的目的,我对该包进行了简化。在生产应用程序中,你应该时刻注意“隐藏”包主体中的包数据(如同这些集合一样),然后提供过程和函数来管理这些数据。
例如,假设我想把这两个集合合并成一个“我们的最爱”的集合。在Oracle数据库10g出现以前,我必须编写一个将一个集合的内容转移到另一个集合的循环。而现在,我可以依赖MULTISET UNION运算符来实现这一点了,如下所示:
DECLARE
our_favorites
strings_nt := strings_nt ();
BEGIN
our_favorites :=
favorites_pkg.my_favorites
MULTISET UNION
favorites_pkg.dad_favorites;
favorites_pkg.show_favorites (
'ME then DAD', our_favorites);
END;
/
此脚本的输出结果为:
ME then DAD
1 = CHOCOLATE
2 = BRUSSEL SPROUTS
3 = SPIDER ROLL
4 = PICKLED HERRING
5 = POTATOES
6 = PASTRAMI
7 = CHOCOLATE
可以看出,两个嵌套表的值被合并到一起了。而且立刻就可以看到,MULTISET UNION运算符不同于SQL UNION运算符(实际上,与SQL的UNION ALL运算符完全相同)。对两个SELECT结果集进行UNION运算时,SQL引擎会自动生成一个惟一的有序结果集。换句话说,如果我的两个嵌套表都是查询,那么UNION将生成这样的结果集:
BRUSSEL SPROUTS
CHOCOLATE
PASTRAMI
PICKLED HERRING
POTATOES
SPIDER ROLL
这些数据是按照字母顺序排列的,且CHOCOLATE仅出现一次。为什么会产生不同的结果呢?因为嵌套表是一种多集合(multiset),mathworld.wolfram.com/Multiset.html 对其做了如下定义:
“一种类似于集合的对象,在其内部,顺序没有意义,而多重性却具有明确的意义;因此,多集合{1, 2, 3}和{2, 1, 3}是等同的,而{1, 1, 2, 3}和{1, 2, 3}却是不同的。”
Oracle文件中说,嵌套表和变长数组的区别在于嵌套表列中存储的数据不保存其顺序,而在变长数组中存储的数据保存其顺序。在Oracle数据库10g之前,这种区别在PL/SQL中没多大意义。现在,有了集合运算符之后,多集合(或嵌套表)的特性便显得极为重要了。
为了更好地理解嵌套表数据没有顺序,MULTISET UNION也不会对结果嵌套表应用顺序这一特性,请我们来看看面的这个程序块:
DECLARE
our_favorites
strings_nt := strings_nt ();
BEGIN
our_favorites :=
favorites_pkg.dad_favorites
MULTISET UNION
favorites_pkg.my_favorites;
favorites_pkg.show_favorites (
'DAD then ME', our_favorites);
END;
/
与前面的程序块惟一不同的是,我改变了MULTISET UNION运算中嵌套表的顺序。结果就变为:
DAD then ME
1 = PICKLED HERRING
2 = POTATOES
3 = PASTRAMI
4 = CHOCOLATE
5 = CHOCOLATE
6 = BRUSSEL SPROUTS
7 = SPIDER ROLL
如果你不希望合并之后的嵌套表中出现重复项,那么可